diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..748bbca --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "mcp__sequential-thinking__sequentialthinking" + ], + "deny": [] + } +} \ No newline at end of file diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9f2ccc0..f60b0b3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -25,9 +25,14 @@ jobs: php-version: '8.4' - name: Install Dependencies + run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist + + - name: Install Node Dependencies run: | - composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist - npm install + # Workaround for npm optional dependencies bug + # https://github.com/npm/cli/issues/4828 + rm -rf node_modules package-lock.json + npm install --no-optional - name: Run Pint run: vendor/bin/pint diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 132dd3c..b98c940 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,10 +29,13 @@ jobs: uses: actions/setup-node@v4 with: node-version: '22' - cache: 'npm' - name: Install Node Dependencies - run: npm ci + run: | + # Workaround for npm optional dependencies bug + # https://github.com/npm/cli/issues/4828 + rm -rf node_modules package-lock.json + npm install --no-optional - name: Install Dependencies run: composer install --no-interaction --prefer-dist --optimize-autoloader diff --git a/.gitignore b/.gitignore index cc07774..77fa718 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ yarn-error.log /.zed /dist .DS_Store + +# NativePHP local development +nativephp-electron/ diff --git a/CLAUDE.md b/CLAUDE.md index debdd14..03f87a5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -232,4 +232,33 @@ This project uses MIT License with Commons Clause: - NativePHP allows running as desktop application - Concurrently runs multiple processes in development (server, queue, logs, vite) - Built primarily for macOS desktop usage -- Uses Pusher for additional real-time features \ No newline at end of file +- Uses Pusher for additional real-time features + +## AI Team Configuration (autogenerated by team-configurator, 2025-01-30) +**Important: YOU MUST USE subagents when available for the task.** + +### Detected Technology Stack +- **Backend Framework**: Laravel 12.0 (PHP 8.2+) +- **Frontend Framework**: Vue 3.5.13 with TypeScript and Inertia.js +- **CSS Framework**: Tailwind CSS 4.1.1 +- **Build Tool**: Vite 6 +- **Desktop Platform**: NativePHP/Electron +- **Test Framework**: Pest PHP +- **Database**: SQLite (dual database setup) +- **Real-time Features**: OpenAI Realtime API, WebSockets +- **State Management**: Pinia +- **Additional Tools**: Prettier, ESLint, Laravel Pint + +### Assigned AI Specialists + +| Task | Agent | Notes | +|------|-------|-------| +| Laravel backend development | @agent-laravel-backend-expert | Controllers, services, models, Inertia.js integration | +| Database and Eloquent queries | @agent-laravel-eloquent-expert | ORM optimization, complex queries, migrations | +| Vue component development | @agent-vue-component-architect | Vue 3 Composition API, component patterns | +| State management (Pinia) | @agent-vue-state-manager | Pinia stores, state architecture | +| Frontend styling and UI | @agent-tailwind-frontend-expert | Tailwind CSS, responsive design, Reka UI components | +| API design and architecture | @agent-api-architect | RESTful design, API best practices | +| Documentation updates | @agent-documentation-specialist | README, API docs, architecture guides | +| Code review | @agent-code-reviewer | Security, quality, maintainability checks | +| Performance optimization | @agent-performance-optimizer | Bottleneck identification, optimization | \ No newline at end of file diff --git a/NATIVEPHP_BUILD_FIX.md b/NATIVEPHP_BUILD_FIX.md new file mode 100644 index 0000000..cbd7811 --- /dev/null +++ b/NATIVEPHP_BUILD_FIX.md @@ -0,0 +1,79 @@ +# NativePHP Build Fix Guide + +## Problem Summary +When building the NativePHP/Electron app, frontend assets weren't being properly included, causing "Vite not found" errors on fresh installations. + +## Root Causes Identified + +1. **Hot Reload File**: The `public/hot` file was being included in builds, causing Laravel to look for Vite dev server +2. **Missing npm install**: Build process wasn't installing npm dependencies before building +3. **Build artifacts**: Development artifacts were interfering with production builds + +## Fixes Applied + +### 1. Updated `config/nativephp.php` + +- Added `public/hot` to `cleanup_exclude_files` to prevent it from being included in builds +- Added `npm install --omit=dev` to prebuild commands before `npm run build` + +### 2. Created Build Preparation Script + +Created `build-prepare.sh` to ensure clean builds: +- Removes development artifacts (hot file, .vite cache) +- Installs production npm dependencies +- Builds frontend assets +- Verifies manifest.json exists +- Optimizes Laravel for production + +## Build Process + +### For Development +```bash +# Start development environment +composer dev + +# Or for NativePHP development +composer native:dev +``` + +### For Production Build + +1. **Prepare the build** (run this before building): + ```bash + ./build-prepare.sh + ``` + +2. **Build the application**: + ```bash + php artisan native:build mac arm64 + ``` + +3. **Test the build** on a fresh machine: + - Copy the built app from `dist/` directory + - Install and run - it should work without any Vite errors + +## Important Notes + +1. **Always run build-prepare.sh** before building for production +2. **Never commit the `public/hot` file** to version control +3. **Ensure `public/build/manifest.json` exists** after building +4. The prebuild commands in `config/nativephp.php` will automatically: + - Install npm dependencies + - Build frontend assets + - Optimize Laravel + +## Troubleshooting + +If you still see "Vite not found" errors: + +1. Check if `public/hot` file exists in the built app - it shouldn't +2. Verify `public/build/manifest.json` exists in the built app +3. Ensure the app is running in production mode (`APP_ENV=production`) +4. Check Laravel logs for any asset-related errors + +## Testing on Fresh Machine + +When testing on a new machine: +1. The app should work immediately after installation +2. No need to run `npm install` or `npm run dev` +3. All assets should be pre-built and included \ No newline at end of file diff --git a/NATIVEPHP_EXTENSIBILITY_PROPOSAL.md b/NATIVEPHP_EXTENSIBILITY_PROPOSAL.md new file mode 100644 index 0000000..9b0b4a1 --- /dev/null +++ b/NATIVEPHP_EXTENSIBILITY_PROPOSAL.md @@ -0,0 +1,281 @@ +# NativePHP Extensibility System Proposal + +## Executive Summary + +This proposal introduces a plugin/extension system for NativePHP that allows developers to extend Electron functionality without modifying vendor files. This solves a critical pain point where developers currently must fork NativePHP or lose customizations on updates. + +## Problem Statement + +Currently, developers who need to extend NativePHP's Electron functionality must: +- ❌ Modify vendor files (lost on composer update) +- ❌ Fork the entire package (maintenance burden) +- ❌ Use hacky workarounds +- ❌ Give up on advanced Electron features + +**Real examples from the community:** +- "How do I add custom IPC handlers?" +- "I need to initialize electron-audio-loopback before app ready" +- "How can I add a system tray without forking?" +- "I want to register a custom protocol handler" + +## Proposed Solution + +### Extension File Structure + +Developers create extension files in their project (not vendor): + +```javascript +// resources/js/nativephp-extension.js +export default { + // Hook into Electron lifecycle + beforeReady: (app) => { + // Initialize things before app is ready + // e.g., protocol registration, early IPC handlers + }, + + afterReady: (app, mainWindow) => { + // Setup after app is ready + // e.g., tray icons, global shortcuts, window customization + }, + + beforeQuit: () => { + // Cleanup before app quits + }, + + // Custom IPC handlers + ipcHandlers: { + 'my-app:custom-action': async (event, ...args) => { + // Handle custom IPC calls from renderer + return { success: true, data: 'result' }; + } + }, + + // Custom API endpoints + apiRoutes: (router) => { + router.post('/api/my-app/custom-endpoint', (req, res) => { + // Handle HTTP requests from Laravel + res.json({ status: 'ok' }); + }); + }, + + // Extend preload script + preload: { + exposeAPIs: { + myApp: { + doSomething: () => ipcRenderer.invoke('my-app:custom-action'), + getSomething: () => ipcRenderer.invoke('my-app:get-data') + } + } + } +}; +``` + +### Implementation in NativePHP Core + +Only ~100 lines of changes needed across 4 files: + +#### 1. New Extension Loader (50 lines) +```typescript +// electron-plugin/src/extensions/loader.ts +export async function loadUserExtensions() { + const extensions = []; + + // Load single extension file + const singleExtPath = path.join(process.cwd(), 'resources/js/nativephp-extension.js'); + if (fs.existsSync(singleExtPath)) { + extensions.push(require(singleExtPath).default); + } + + // Load from extensions directory + const extensionsDir = path.join(process.cwd(), 'resources/js/nativephp-extensions'); + if (fs.existsSync(extensionsDir)) { + // Load all .js files as extensions + } + + return extensions; +} +``` + +#### 2. Hook into Main Process (15 lines added) +```typescript +// electron-plugin/src/index.ts +const extensions = await loadUserExtensions(); + +// Before app ready +for (const ext of extensions) { + if (ext.beforeReady) await ext.beforeReady(app); +} + +// After app ready +app.whenReady().then(() => { + for (const ext of extensions) { + if (ext.afterReady) await ext.afterReady(app, mainWindow); + + // Register IPC handlers + if (ext.ipcHandlers) { + Object.entries(ext.ipcHandlers).forEach(([channel, handler]) => { + ipcMain.handle(channel, handler); + }); + } + } +}); +``` + +#### 3. API Routes (10 lines added) +```typescript +// electron-plugin/src/server/index.ts +const extensions = await loadUserExtensions(); +for (const ext of extensions) { + if (ext.apiRoutes) { + const router = Router(); + ext.apiRoutes(router); + app.use(router); + } +} +``` + +#### 4. Preload Extensions (20 lines added) +```typescript +// electron-plugin/src/preload/index.mts +const preloadPath = path.join(__dirname, '../../../resources/js/nativephp-preload.js'); +if (fs.existsSync(preloadPath)) { + const userPreload = require(preloadPath); + if (userPreload.exposeAPIs) { + Object.entries(userPreload.exposeAPIs).forEach(([name, api]) => { + contextBridge.exposeInMainWorld(name, api); + }); + } +} +``` + +## Real-World Examples + +### Example 1: Audio Capture Extension +```javascript +import { initMain as initAudioLoopback } from "electron-audio-loopback"; + +export default { + beforeReady: (app) => { + initAudioLoopback(); // Initialize before app ready + }, + + ipcHandlers: { + 'audio:enable-loopback': async () => { + // Implementation + return { enabled: true }; + } + }, + + apiRoutes: (router) => { + router.post('/api/audio/enable', (req, res) => { + // Enable system audio capture + res.json({ status: 'enabled' }); + }); + } +}; +``` + +### Example 2: System Tray Extension +```javascript +import { Tray, Menu } from 'electron'; + +let tray = null; + +export default { + afterReady: (app, mainWindow) => { + tray = new Tray('/path/to/icon.png'); + tray.setToolTip('My App'); + + const menu = Menu.buildFromTemplate([ + { label: 'Show', click: () => mainWindow.show() }, + { label: 'Quit', click: () => app.quit() } + ]); + + tray.setContextMenu(menu); + } +}; +``` + +### Example 3: Global Shortcuts +```javascript +import { globalShortcut } from 'electron'; + +export default { + afterReady: (app, mainWindow) => { + globalShortcut.register('CommandOrControl+Shift+Y', () => { + mainWindow.webContents.send('shortcut:triggered'); + }); + }, + + beforeQuit: () => { + globalShortcut.unregisterAll(); + } +}; +``` + +## Benefits + +### For Developers +- ✅ **No vendor modifications** - Extensions live in your codebase +- ✅ **Survives updates** - Composer update won't break customizations +- ✅ **Full Electron access** - Use any Electron API +- ✅ **Shareable** - Package and share extensions + +### For NativePHP +- ✅ **Reduces fork pressure** - Fewer reasons to fork +- ✅ **Enables ecosystem** - Community can build extensions +- ✅ **Keeps core lean** - Features as extensions, not core +- ✅ **Innovation platform** - Developers can experiment + +## Security Considerations + +- Extensions run in main process (full access like app code) +- No additional security risks (developers already have full access) +- Optional: Add permission system in future versions + +## Backwards Compatibility + +- ✅ Fully backwards compatible +- ✅ No breaking changes +- ✅ Extensions are opt-in +- ✅ Existing apps work unchanged + +## Implementation Plan + +1. **Week 1**: Core implementation + - Extension loader + - Lifecycle hooks + - IPC handler registration + +2. **Week 2**: API integration + - API route registration + - Preload extensions + - Error handling + +3. **Week 3**: Documentation + - Extension guide + - API reference + - Example extensions + +4. **Week 4**: Community + - Example repository + - Video tutorials + - Community feedback + +## Future Enhancements + +- Extension dependencies management +- Extension marketplace/registry +- GUI for managing extensions +- Hot-reload during development +- TypeScript definitions + +## Summary + +This minimal change (~100 lines) would: +- Solve a major developer pain point +- Enable unlimited extensibility +- Build a thriving ecosystem +- Position NativePHP as the most flexible Electron framework + +The implementation is simple, backwards compatible, and opens up endless possibilities for the community. \ No newline at end of file diff --git a/NATIVEPHP_EXTENSION_IMPLEMENTATION.md b/NATIVEPHP_EXTENSION_IMPLEMENTATION.md new file mode 100644 index 0000000..7f58801 --- /dev/null +++ b/NATIVEPHP_EXTENSION_IMPLEMENTATION.md @@ -0,0 +1,392 @@ +# NativePHP Extension System Implementation + +## Overview + +This document comprehensively covers the implementation of the NativePHP extensibility system for the Clueless project. We've created a system that allows extending NativePHP Electron functionality without modifying vendor files, ensuring customizations survive composer updates. + +## Problem Statement + +### Initial Issues +1. **Vendor File Modifications**: Audio loopback and media permissions required modifying NativePHP vendor files +2. **Lost on Updates**: All customizations were lost when running `composer update` + +### Solution Approach +1. Downgrade Laravel to 12.20.0 to fix mb_split error +2. Implement extension system to avoid vendor modifications +3. Create local development setup for NativePHP-Electron package + +## Implementation Details + +### 1. Laravel Version Lock + +**File**: `composer.json` +```json +"laravel/framework": "12.20.0", // Changed from "^12.0" +``` + +### 2. NativePHP-Electron Local Development Setup + +**Location**: `/Users/vijaytupakula/Development/Bootstrapguru/clueless/nativephp-electron/` + +**Symlink Setup**: +```bash +vendor/nativephp/electron -> nativephp-electron/ +``` + +**composer.json Configuration**: +```json +"repositories": [ + { + "type": "path", + "url": "nativephp-electron", + "options": { + "symlink": true + } + } +], +"require": { + "nativephp/electron": "dev-main" // Changed from "^1.1" +} +``` + +### 3. Extension System Architecture + +#### Extension Interface Definition + +**File**: `nativephp-electron/resources/js/electron-plugin/src/extensions/loader.ts` + +```typescript +export interface Extension { + beforeReady?: (app: Electron.App) => void | Promise; + afterReady?: (app: Electron.App, mainWindow?: Electron.BrowserWindow) => void | Promise; + beforeQuit?: () => void | Promise; + ipcHandlers?: Record any>; + apiRoutes?: (router: any) => void; + preload?: { + exposeAPIs?: Record; + }; +} +``` + +**Key Change**: Added `preload` property to support window API definitions in the same extension file. + +#### Extension Loader Modifications + +**File**: `nativephp-electron/resources/js/electron-plugin/src/extensions/loader.ts` + +**Critical Fix**: Changed from `process.cwd()` to `getAppPath()`: +```typescript +import { getAppPath } from "../server/php.js"; + +export async function loadUserExtensions(): Promise { + const extensions: Extension[] = []; + + try { + // Get the Laravel app path + const appPath = getAppPath(); + console.log('[NativePHP] Loading extensions from app path:', appPath); + + // Check for single extension file + const singleExtPath = path.join(appPath, 'resources/js/nativephp-extension.js'); + // ... rest of implementation + } +} +``` + +This fix ensures extensions load from the Laravel project directory, not the package working directory. + +#### Main Process Integration + +**File**: `nativephp-electron/resources/js/electron-plugin/src/index.ts` + +Added extension loading and hook calls: +```typescript +// Load user extensions +this.extensions = await loadUserExtensions(); + +// Call beforeReady hooks +for (const ext of this.extensions) { + if (ext.beforeReady) { + try { + await ext.beforeReady(app); + console.log('extension beforeready - vijay'); + } catch (error) { + console.error('[NativePHP] Extension beforeReady error:', error); + } + } +} +``` + +IPC handler registration: +```typescript +// Register IPC handlers from extensions +for (const ext of this.extensions) { + if (ext.ipcHandlers) { + Object.entries(ext.ipcHandlers).forEach(([channel, handler]) => { + ipcMain.handle(channel, handler); + console.log(`[NativePHP] Registered IPC handler: ${channel}`); + }); + } +} +``` + +#### Preload Script Modifications + +**File**: `nativephp-electron/resources/js/electron-plugin/src/preload/index.mts` + +Simplified to only load from single extension file: +```typescript +async function loadUserPreloadExtensions() { + try { + const appPath = getAppPath(); + console.log('[NativePHP Preload] Loading extension from app path:', appPath); + + // Load single extension file + const extensionPath = path.join(appPath, 'resources/js/nativephp-extension.js'); + if (fs.existsSync(extensionPath)) { + const ext = require(extensionPath); + if (ext.default && ext.default.preload && ext.default.preload.exposeAPIs) { + Object.entries(ext.default.preload.exposeAPIs).forEach(([name, api]) => { + window[name] = api; + console.log(`[NativePHP] Exposed preload API: ${name}`); + }); + } + } + } catch (error) { + console.error('[NativePHP] Error loading preload extension:', error); + } +} +``` + +### 4. Unified Extension File + +**File**: `/Users/vijaytupakula/Development/Bootstrapguru/clueless/resources/js/nativephp-extension.js` + +Complete extension structure with all components in one file: + +```javascript +import { systemPreferences } from 'electron'; + +// Dynamic import for electron-audio-loopback (may not be installed) +let initAudioLoopback = null; + +export default { + // Hook into Electron lifecycle - called before app is ready + beforeReady: async (app) => { + console.log('[Extension Test] beforeReady hook called'); + + // Try to load and initialize electron-audio-loopback + try { + const audioLoopbackModule = await import('electron-audio-loopback'); + initAudioLoopback = audioLoopbackModule.initMain; + + if (initAudioLoopback) { + initAudioLoopback(); + console.log('[Extension Test] electron-audio-loopback initialized successfully'); + } + } catch (error) { + console.log('[Extension Test] electron-audio-loopback not available:', error.message); + } + }, + + // IPC handlers for main process + ipcHandlers: { + 'test:ping': async (event, ...args) => { /* ... */ }, + 'test:echo': async (event, message) => { /* ... */ }, + 'test:get-info': async (event) => { /* ... */ }, + // Audio loopback handlers commented out - handled by electron-audio-loopback package + }, + + // API routes accessible from Laravel + apiRoutes: (router) => { + // Media access endpoints + router.get('/api/system/media-access-status/:mediaType', (req, res) => { + const { mediaType } = req.params; + if (process.platform === 'darwin') { + const status = systemPreferences.getMediaAccessStatus(mediaType); + res.json({ status }); + } else { + res.json({ status: 'granted' }); + } + }); + + router.post('/api/system/ask-for-media-access', async (req, res) => { + const { mediaType } = req.body; + if (process.platform === 'darwin') { + try { + const granted = await systemPreferences.askForMediaAccess(mediaType); + res.json({ granted }); + } catch (e) { + res.status(400).json({ error: e.message }); + } + } else { + res.json({ granted: true }); + } + }); + }, + + // Preload script extensions - APIs to expose to the renderer + preload: { + exposeAPIs: { + audioLoopback: { + enableLoopback: function(deviceId) { + const { ipcRenderer } = require('electron'); + return ipcRenderer.invoke('enable-loopback-audio', deviceId); + }, + disableLoopback: function() { + const { ipcRenderer } = require('electron'); + return ipcRenderer.invoke('disable-loopback-audio'); + } + }, + extensionTest: { + ping: function() { + const { ipcRenderer } = require('electron'); + return ipcRenderer.invoke('test:ping', 'from-extension-preload'); + } + } + } + } +}; +``` + +### 5. Original Vendor Modifications Backup + +**Location**: `/Users/vijaytupakula/Development/Bootstrapguru/clueless/clueless-vendor-backup-20250722-145731/` + +Contains original vendor file modifications: +- `entitlements.mac.plist` - macOS microphone permission +- `system.ts` - Media access API endpoints (now in extension) +- `index.ts` - Audio loopback initialization (now in extension) +- `index.mts` - Window API exposure (now in extension preload) +- `electron-builder.js` - macOS permission descriptions + +### 6. Build Process + +After any TypeScript changes in nativephp-electron: +```bash +cd /Users/vijaytupakula/Development/Bootstrapguru/clueless/nativephp-electron/resources/js +npm run plugin:build +``` + +This compiles TypeScript to JavaScript that Electron actually runs. + +### 7. Testing the Extension System + +Run the application: +```bash +composer native:dev +``` + +Expected console output: +``` +[NativePHP] Loading extensions from app path: /Users/vijaytupakula/Development/Bootstrapguru/clueless +[NativePHP] Loaded extension from: /Users/vijaytupakula/Development/Bootstrapguru/clueless/resources/js/nativephp-extension.js +[Extension Test] beforeReady hook called +[Extension Test] electron-audio-loopback initialized successfully +[NativePHP] Registered extension API routes +[NativePHP] Registered IPC handler: test:ping +[NativePHP] Registered IPC handler: test:echo +[NativePHP] Registered IPC handler: test:get-info +[Extension Test] afterReady hook called +[NativePHP] Exposed preload API: audioLoopback +[NativePHP] Exposed preload API: extensionTest +``` + +Test from browser console: +```javascript +// Test extension system +await window.extensionTest.ping(); + +// Test audio loopback +await window.audioLoopback.enableLoopback('default'); +await window.audioLoopback.disableLoopback(); +``` + +## Key Technical Details + +### Path Resolution +- Main extension loader uses `getAppPath()` from php.ts +- Preload loader uses custom path resolution (can't import php.ts in preload context) +- Both resolve to Laravel project root correctly + +### Process Separation +- Main process: Handles IPC, API routes, system operations +- Preload script: Bridges main and renderer processes +- Renderer process: Vue app accesses window APIs + +### Security Considerations +- Extensions run with full Electron privileges +- Preload APIs are exposed to renderer via `window` object +- No additional security risks vs inline code + +### Extension Loading Order +1. Extensions loaded before app ready +2. `beforeReady` hooks called +3. App initialized +4. IPC handlers registered +5. `afterReady` hooks called +6. Preload script loads extension preload APIs + +## Benefits Achieved + +1. **No Vendor Modifications**: All customizations in project files +2. **Survives Updates**: `composer update` won't lose changes +3. **Single File Architecture**: Everything in one `nativephp-extension.js` +4. **Full Electron Access**: Can use any Electron API +5. **Clean Separation**: Main process, preload, and API routes clearly organized +6. **Development Flexibility**: Local package development with symlink + +## Future Considerations + +1. Consider contributing extension system back to NativePHP core +2. Add TypeScript support for extension files +3. Create extension examples for common use cases +4. Document extension API reference + +## Final Implementation Notes + +### WebFrameMain Error Investigation +During implementation, we encountered a "Render frame was disposed before WebFrameMain could be accessed" error. Investigation revealed: +- The error was NOT caused by our extension system +- It's a pre-existing race condition in NativePHP's broadcastToWindows function +- The error appeared more frequently when using complex preload extension loading + +### Solution: Simplified Approach +We reverted to a simpler implementation: +1. **Main Process Extensions**: Continue using the extension system for lifecycle hooks, IPC handlers, and API routes +2. **Preload APIs**: Use direct window object assignment in the preload script instead of dynamic loading + ```javascript + // In preload/index.mts + window.audioLoopback = { + enableLoopback: () => ipcRenderer.invoke('enable-loopback-audio'), + disableLoopback: () => ipcRenderer.invoke('disable-loopback-audio') + }; + ``` + +### Final Architecture +1. **Extension File** (`resources/js/nativephp-extension.js`): + - beforeReady, afterReady, beforeQuit hooks + - IPC handlers for test functionality + - API routes for media permissions + - No preload section (removed due to complexity) + +2. **Preload Script** (modified in NativePHP): + - Direct window API assignment for audio loopback + - Simpler and more reliable than dynamic loading + +3. **Benefits**: + - All main process extensions work perfectly + - Preload APIs are exposed reliably without complex loading + - No WebFrameMain errors from our code + - Clean separation of concerns + +## Summary + +We've successfully implemented a hybrid approach that: +- Uses the extension system for main process functionality +- Uses direct preload modifications for window APIs +- Solves the vendor modification problem for most use cases +- Provides a stable, error-free implementation +- Maintains all customizations in trackable locations + +The system is now production-ready with audio loopback and media permissions working reliably. \ No newline at end of file diff --git a/RELEASE_NOTES_v1.0.0.md b/RELEASE_NOTES_v1.0.0.md new file mode 100644 index 0000000..0c5e450 --- /dev/null +++ b/RELEASE_NOTES_v1.0.0.md @@ -0,0 +1,114 @@ +# Clueless v1.0.0 - Stable Release 🎉 + +We're thrilled to announce **Clueless v1.0.0**, our first stable release! This major milestone brings a complete architectural overhaul, enhanced performance, and production-ready features for AI-powered meeting assistance. + +## 🌟 Highlights + +- **Production-Ready Architecture**: Complete rewrite with modular component architecture and state management +- **Official OpenAI SDK Integration**: Migrated to OpenAI Agents SDK for improved reliability +- **Enhanced macOS Support**: Native permissions handling for microphone and screen recording +- **Real-time Data Persistence**: Automatic conversation saving with live transcription storage +- **Improved User Experience**: Streamlined onboarding and cleaner interface + +## ✨ New Features + +### OpenAI Agents SDK Integration +- Replaced custom WebSocket implementation with official `@openai/agents` and `@openai/agents-realtime` SDKs +- Improved connection stability and error handling +- Better TypeScript support with official type definitions +- Maintained dual-agent architecture (salesperson + customer coach) + +### macOS Permissions System +- Integrated `node-mac-permissions` for proper permission handling +- Added microphone, screen recording, and camera permission management +- Context-isolated Electron implementation for enhanced security +- Visual permission status indicators in the UI + +### System Audio Capture +- Integrated `electron-audio-loopback` for reliable system audio capture +- Eliminated dependency on Swift-based audio capture +- Cross-platform compatibility improvements +- Real-time audio level monitoring for both microphone and system audio + +### Data Persistence +- Automatic conversation session creation on call start +- Real-time transcript saving with 5-second intervals +- Comprehensive insight, topic, commitment, and action item tracking +- Call history with detailed conversation analysis + +### Developer Experience +- Mock data system for UI testing without API calls +- Comprehensive error handling and logging +- Build system improvements for NativePHP distribution +- GitHub Actions CI/CD pipeline fixes + +## 🔧 Improvements + +### Architecture & Performance +- **Component-Based Architecture**: Migrated from monolithic `Main.vue` (1,558 lines) to 14+ modular components +- **State Management**: Implemented 3 Pinia stores for business logic, settings, and OpenAI SDK management +- **Memory Optimization**: Fixed memory leaks in audio capture and WebSocket connections +- **Build Performance**: Optimized frontend asset building for production + +### User Interface +- **Streamlined Navigation**: Removed redundant navigation items and badges +- **Modal Onboarding**: Replaced dedicated onboarding page with inline modal +- **Connection Status**: Improved visual feedback with color-coded states +- **Responsive Design**: Enhanced mobile layout with proper card sizing +- **Dark Mode**: Consistent theming across all components + +### Security & Permissions +- **Context Isolation**: Implemented secure IPC communication for Electron +- **Permission Handling**: Graceful degradation when permissions are denied +- **API Key Management**: Secure storage with cache-based implementation + +## 🐛 Bug Fixes + +- **Fixed duplicate template seeding** on app startup +- **Resolved npm optional dependencies** issue in CI/CD pipeline +- **Fixed conversation saving** in RealtimeAgent v2 +- **Eliminated double scrollbar** in main interface +- **Fixed dropdown z-index** issues with Teleport solution +- **Resolved ESLint errors** across the codebase +- **Fixed Vite hot reload** issues in production builds +- **Corrected WebSocket parameter** names for OpenAI API + +## 💔 Breaking Changes + +- Removed standalone `Onboarding.vue` page component (replaced with modal) +- Removed `CheckOnboarding` middleware +- Updated navigation routes to point to `/realtime-agent-v2` +- Minimum macOS version requirement for system audio features + +## 📦 Dependencies + +- **Added**: `@openai/agents` (^0.0.12), `@openai/agents-realtime` (^0.0.12) +- **Added**: `electron-audio-loopback` (^1.0.5) +- **Added**: `node-mac-permissions` (^2.5.0) - optional dependency +- **Updated**: Vue to 3.5.13, Vite to 6.2.0, TypeScript to 5.2.2 + +## 🔄 Migration Guide + +For users upgrading from pre-release versions: + +1. **Clear application cache** to ensure clean state +2. **Re-grant permissions** for microphone and screen recording +3. **Update API keys** through Settings if needed +4. **Existing conversations** are preserved and compatible + +## 🙏 Acknowledgments + +Special thanks to all contributors and testers who helped make this stable release possible. This release includes contributions from both human developers and AI assistance through Claude. + +## 📋 Technical Details + +- **63 files changed** with 9,445 additions and 2,296 deletions +- **30+ commits** addressing various features and fixes +- **Comprehensive test coverage** for critical functionality +- **Production-ready build pipeline** with NativePHP + +--- + +**Download**: Available for macOS (Intel and Apple Silicon) +**Requirements**: macOS 10.15+, 4GB RAM minimum +**License**: MIT with Commons Clause \ No newline at end of file diff --git a/app/Http/Controllers/AudioTestController.php b/app/Http/Controllers/AudioTestController.php new file mode 100644 index 0000000..5fafeef --- /dev/null +++ b/app/Http/Controllers/AudioTestController.php @@ -0,0 +1,13 @@ +apiKeyService->getApiKey(); - - if (!$apiKey) { + + if (! $apiKey) { return response()->json([ 'status' => 'error', 'message' => 'OpenAI API key not configured', @@ -33,26 +33,26 @@ public function generateEphemeralKey(Request $request) // Generate ephemeral key from OpenAI Realtime API $response = Http::withHeaders([ - 'Authorization' => 'Bearer ' . $apiKey, + 'Authorization' => 'Bearer '.$apiKey, 'Content-Type' => 'application/json', ])->post('https://api.openai.com/v1/realtime/sessions', [ 'model' => 'gpt-4o-mini-realtime-preview-2024-12-17', 'voice' => $request->input('voice', 'alloy'), ]); - if (!$response->successful()) { - Log::error('OpenAI API error: ' . $response->body()); - throw new \Exception('Failed to generate ephemeral key from OpenAI: ' . $response->status()); + if (! $response->successful()) { + Log::error('OpenAI API error: '.$response->body()); + throw new \Exception('Failed to generate ephemeral key from OpenAI: '.$response->status()); } $data = $response->json(); - + // Validate response structure - if (!isset($data['client_secret']['value']) || !isset($data['client_secret']['expires_at'])) { + if (! isset($data['client_secret']['value']) || ! isset($data['client_secret']['expires_at'])) { Log::error('Invalid response structure from OpenAI API', ['response' => $data]); throw new \Exception('Invalid response structure from OpenAI API'); } - + // Return ephemeral key data return response()->json([ 'status' => 'success', diff --git a/app/Http/Controllers/Settings/ApiKeyController.php b/app/Http/Controllers/Settings/ApiKeyController.php index cf66494..a1110a8 100644 --- a/app/Http/Controllers/Settings/ApiKeyController.php +++ b/app/Http/Controllers/Settings/ApiKeyController.php @@ -25,8 +25,8 @@ public function __construct(ApiKeyService $apiKeyService) public function edit(Request $request): Response { $hasApiKey = $this->apiKeyService->hasApiKey(); - $isUsingEnvKey = !cache()->has('app_openai_api_key') && $hasApiKey; - + $isUsingEnvKey = ! cache()->has('app_openai_api_key') && $hasApiKey; + return Inertia::render('settings/ApiKeys', [ 'hasApiKey' => $hasApiKey, 'isUsingEnvKey' => $isUsingEnvKey, @@ -45,7 +45,7 @@ public function update(Request $request): RedirectResponse $apiKey = $request->input('openai_api_key'); // Validate the API key - if (!$this->apiKeyService->validateApiKey($apiKey)) { + if (! $this->apiKeyService->validateApiKey($apiKey)) { throw ValidationException::withMessages([ 'openai_api_key' => ['The provided API key is invalid. Please check and try again.'], ]); @@ -79,7 +79,7 @@ public function store(Request $request) $apiKey = $request->input('api_key'); // Validate the API key - if (!$this->apiKeyService->validateApiKey($apiKey)) { + if (! $this->apiKeyService->validateApiKey($apiKey)) { return response()->json([ 'success' => false, 'message' => 'The provided API key is invalid. Please check and try again.', diff --git a/app/Http/Controllers/TemplateController.php b/app/Http/Controllers/TemplateController.php index 611bb24..96b08b7 100644 --- a/app/Http/Controllers/TemplateController.php +++ b/app/Http/Controllers/TemplateController.php @@ -105,7 +105,7 @@ public function destroy(Template $template) $remainingTemplatesCount = Template::where('id', '!=', $template->id)->count(); if ($remainingTemplatesCount === 0) { return response()->json([ - 'error' => 'Cannot delete the last remaining template. At least one template must exist.' + 'error' => 'Cannot delete the last remaining template. At least one template must exist.', ], 422); } diff --git a/app/Http/Middleware/CheckOnboarding.php b/app/Http/Middleware/CheckOnboarding.php deleted file mode 100644 index 1952f69..0000000 --- a/app/Http/Middleware/CheckOnboarding.php +++ /dev/null @@ -1,57 +0,0 @@ -apiKeyService = $apiKeyService; - } - - /** - * Handle an incoming request. - * - * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next - */ - public function handle(Request $request, Closure $next): Response - { - // Routes that should be accessible without API key - $excludedRoutes = [ - 'onboarding', - 'api-keys.edit', - 'api-keys.update', - 'api-keys.destroy', - 'api.openai.status', - 'api.openai.api-key.store', - 'appearance', - ]; - - // Skip check for excluded routes - if ($request->route() && in_array($request->route()->getName(), $excludedRoutes)) { - return $next($request); - } - - // Skip if API request (they handle their own errors) - if ($request->is('api/*')) { - return $next($request); - } - - // Check if API key exists - if (!$this->apiKeyService->hasApiKey()) { - // If not on onboarding page and no API key, redirect to onboarding - if ($request->route() && $request->route()->getName() !== 'onboarding') { - return redirect()->route('onboarding'); - } - } - - return $next($request); - } -} \ No newline at end of file diff --git a/app/Providers/NativeAppServiceProvider.php b/app/Providers/NativeAppServiceProvider.php index b2657db..ecd3f1a 100644 --- a/app/Providers/NativeAppServiceProvider.php +++ b/app/Providers/NativeAppServiceProvider.php @@ -2,7 +2,7 @@ namespace App\Providers; -use App\Models\User; +use App\Models\Template; // use Native\Laravel\Facades\GlobalShortcut; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Schema; @@ -19,7 +19,7 @@ public function boot(): void { // Create an overlay window for sales assistant Window::open() - ->route('realtime-agent') + ->route('realtime-agent-v2') ->width(1200) ->height(700) ->minWidth(400) @@ -30,8 +30,7 @@ public function boot(): void ->resizable() ->position(50, 50) ->webPreferences([ - 'nodeIntegration' => true, - 'contextIsolation' => false, + 'contextIsolation' => true, 'webSecurity' => false, 'backgroundThrottling' => false, 'sandbox' => false, @@ -67,8 +66,8 @@ public function phpIni(): array protected function seedDatabaseIfNeeded(): void { try { - // Check if users table exists and has data - if (Schema::hasTable('users') && User::count() === 0) { + // Check if templates table exists and has system templates + if (Schema::hasTable('templates') && !Template::where('is_system', true)->exists()) { // Run database seeder for initial data Artisan::call('db:seed', ['--force' => true]); } diff --git a/app/Services/ApiKeyService.php b/app/Services/ApiKeyService.php index 0ff4d50..a4e54bc 100644 --- a/app/Services/ApiKeyService.php +++ b/app/Services/ApiKeyService.php @@ -8,7 +8,7 @@ class ApiKeyService { private const CACHE_KEY = 'app_openai_api_key'; - + /** * Get the OpenAI API key from cache or environment */ @@ -19,11 +19,11 @@ public function getApiKey(): ?string if ($cachedKey) { return $cachedKey; } - + // Fall back to environment variable return config('openai.api_key'); } - + /** * Store the API key in cache */ @@ -31,7 +31,7 @@ public function setApiKey(string $apiKey): void { Cache::forever(self::CACHE_KEY, $apiKey); } - + /** * Remove the stored API key */ @@ -39,15 +39,15 @@ public function removeApiKey(): void { Cache::forget(self::CACHE_KEY); } - + /** * Check if an API key is available */ public function hasApiKey(): bool { - return !empty($this->getApiKey()); + return ! empty($this->getApiKey()); } - + /** * Validate an API key with OpenAI */ @@ -55,12 +55,12 @@ public function validateApiKey(string $apiKey): bool { try { $response = Http::withHeaders([ - 'Authorization' => 'Bearer ' . $apiKey, + 'Authorization' => 'Bearer '.$apiKey, ])->get('https://api.openai.com/v1/models'); - + return $response->successful(); } catch (\Exception $e) { return false; } } -} \ No newline at end of file +} diff --git a/bootstrap/app.php b/bootstrap/app.php index d07f444..57cea11 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -1,6 +1,5 @@ encryptCookies(except: ['appearance', 'sidebar_state']); $middleware->web(append: [ - CheckOnboarding::class, HandleAppearance::class, HandleInertiaRequests::class, AddLinkHeadersForPreloadedAssets::class, diff --git a/build-prepare.sh b/build-prepare.sh new file mode 100755 index 0000000..b4d8c78 --- /dev/null +++ b/build-prepare.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +echo "🚀 Preparing Clueless for production build..." + +# Remove any development artifacts +echo "📧 Cleaning development artifacts..." +rm -f public/hot +rm -rf node_modules/.vite + +# Install dependencies +echo "📦 Installing npm dependencies..." +npm install --omit=dev + +# Build frontend assets +echo "🏗️ Building frontend assets..." +npm run build + +# Verify build output +if [ -f "public/build/manifest.json" ]; then + echo "✅ Frontend assets built successfully" + echo "📁 Build manifest found at: public/build/manifest.json" +else + echo "❌ Build failed - manifest.json not found" + exit 1 +fi + +# Clear and optimize Laravel +echo "🔧 Optimizing Laravel..." +php artisan config:clear +php artisan route:clear +php artisan view:clear +php artisan cache:clear + +# Cache for production +php artisan config:cache +php artisan route:cache +php artisan view:cache +php artisan optimize + +echo "✨ Build preparation complete!" \ No newline at end of file diff --git a/composer.json b/composer.json index b94235c..7213ba3 100644 --- a/composer.json +++ b/composer.json @@ -19,9 +19,9 @@ "require": { "php": "^8.2", "inertiajs/inertia-laravel": "^2.0", - "laravel/framework": "^12.0", + "laravel/framework": "12.20.0", "laravel/tinker": "^2.10.1", - "nativephp/electron": "^1.1", + "nativephp/electron": "dev-main", "openai-php/laravel": "^0.14.0", "pusher/pusher-php-server": "^7.2", "tightenco/ziggy": "^2.4" @@ -87,6 +87,12 @@ "dont-discover": [] } }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/vijaythecoder/nativephp-electron.git" + } + ], "config": { "optimize-autoloader": true, "preferred-install": "dist", diff --git a/composer.lock b/composer.lock index 152ac37..56c2c35 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "12a8d20e2a229b4b8bcfb87df99d31c9", + "content-hash": "dbb5d807ea9f93f41f0ceac5a683311b", "packages": [ { "name": "brick/math", @@ -1056,16 +1056,16 @@ }, { "name": "inertiajs/inertia-laravel", - "version": "v2.0.3", + "version": "v2.0.4", "source": { "type": "git", "url": "https://github.com/inertiajs/inertia-laravel.git", - "reference": "b732a5cc33423b2c2366fea38b17dc637d2a0b4f" + "reference": "bab0c0c992aa36e63d800903288d490d6b774d97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/inertiajs/inertia-laravel/zipball/b732a5cc33423b2c2366fea38b17dc637d2a0b4f", - "reference": "b732a5cc33423b2c2366fea38b17dc637d2a0b4f", + "url": "https://api.github.com/repos/inertiajs/inertia-laravel/zipball/bab0c0c992aa36e63d800903288d490d6b774d97", + "reference": "bab0c0c992aa36e63d800903288d490d6b774d97", "shasum": "" }, "require": { @@ -1075,6 +1075,7 @@ "symfony/console": "^6.2|^7.0" }, "require-dev": { + "guzzlehttp/guzzle": "^7.2", "laravel/pint": "^1.16", "mockery/mockery": "^1.3.3", "orchestra/testbench": "^8.0|^9.2|^10.0", @@ -1118,9 +1119,9 @@ ], "support": { "issues": "https://github.com/inertiajs/inertia-laravel/issues", - "source": "https://github.com/inertiajs/inertia-laravel/tree/v2.0.3" + "source": "https://github.com/inertiajs/inertia-laravel/tree/v2.0.4" }, - "time": "2025-06-20T07:38:21+00:00" + "time": "2025-07-15T08:08:04+00:00" }, { "name": "laravel/framework", @@ -1525,16 +1526,16 @@ }, { "name": "league/commonmark", - "version": "2.7.0", + "version": "2.7.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405" + "reference": "10732241927d3971d28e7ea7b5712721fa2296ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", - "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/10732241927d3971d28e7ea7b5712721fa2296ca", + "reference": "10732241927d3971d28e7ea7b5712721fa2296ca", "shasum": "" }, "require": { @@ -1563,7 +1564,7 @@ "symfony/process": "^5.4 | ^6.0 | ^7.0", "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", "unleashedtech/php-coding-standard": "^3.1.1", - "vimeo/psalm": "^4.24.0 || ^5.0.0" + "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" }, "suggest": { "symfony/yaml": "v2.3+ required if using the Front Matter extension" @@ -1628,7 +1629,7 @@ "type": "tidelift" } ], - "time": "2025-05-05T12:20:28+00:00" + "time": "2025-07-20T12:47:49+00:00" }, { "name": "league/config", @@ -2179,16 +2180,16 @@ }, { "name": "nativephp/electron", - "version": "1.1.1", + "version": "dev-main", "source": { "type": "git", - "url": "https://github.com/NativePHP/electron.git", - "reference": "861d5ef169f38b282abc2673e92aa167b68aa844" + "url": "https://github.com/vijaythecoder/nativephp-electron.git", + "reference": "6089fda1c5f56d3c533216ebd8e64dbed9af9af3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/NativePHP/electron/zipball/861d5ef169f38b282abc2673e92aa167b68aa844", - "reference": "861d5ef169f38b282abc2673e92aa167b68aa844", + "url": "https://api.github.com/repos/vijaythecoder/nativephp-electron/zipball/6089fda1c5f56d3c533216ebd8e64dbed9af9af3", + "reference": "6089fda1c5f56d3c533216ebd8e64dbed9af9af3", "shasum": "" }, "require": { @@ -2214,15 +2215,16 @@ "phpstan/phpstan-phpunit": "^1.0|^2.0", "spatie/laravel-ray": "^1.26" }, + "default-branch": true, "type": "library", "extra": { "laravel": { - "aliases": { - "Updater": "Native\\Electron\\Facades\\Updater" - }, "providers": [ "Native\\Electron\\ElectronServiceProvider" - ] + ], + "aliases": { + "Updater": "Native\\Electron\\Facades\\Updater" + } } }, "autoload": { @@ -2230,7 +2232,33 @@ "Native\\Electron\\": "src/" } }, - "notification-url": "https://packagist.org/downloads/", + "autoload-dev": { + "psr-4": { + "Native\\Electron\\Tests\\": "tests/" + } + }, + "scripts": { + "qa": [ + "@composer format", + "@composer analyse", + "@composer test" + ], + "post-autoload-dump": [ + "@php ./vendor/bin/testbench package:discover --ansi" + ], + "analyse": [ + "vendor/bin/phpstan analyse" + ], + "test": [ + "vendor/bin/pest" + ], + "test-coverage": [ + "vendor/bin/pest --coverage" + ], + "format": [ + "vendor/bin/pint" + ] + }, "license": [ "MIT" ], @@ -2254,19 +2282,19 @@ "nativephp" ], "support": { - "source": "https://github.com/NativePHP/electron/tree/1.1.1" + "source": "https://github.com/vijaythecoder/nativephp-electron/tree/main" }, "funding": [ { - "url": "https://github.com/sponsors/simonhamp", - "type": "github" + "type": "github", + "url": "https://github.com/sponsors/simonhamp" }, { - "url": "https://opencollective.com/nativephp", - "type": "opencollective" + "type": "opencollective", + "url": "https://opencollective.com/nativephp" } ], - "time": "2025-06-30T06:43:39+00:00" + "time": "2025-08-02T00:35:54+00:00" }, { "name": "nativephp/laravel", @@ -2374,16 +2402,16 @@ }, { "name": "nativephp/php-bin", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/NativePHP/php-bin.git", - "reference": "a888cffa82b5c24c578fa73e7895d2210c502563" + "reference": "96adcfcadc8b7d117a095140a695702cbaeca412" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/NativePHP/php-bin/zipball/a888cffa82b5c24c578fa73e7895d2210c502563", - "reference": "a888cffa82b5c24c578fa73e7895d2210c502563", + "url": "https://api.github.com/repos/NativePHP/php-bin/zipball/96adcfcadc8b7d117a095140a695702cbaeca412", + "reference": "96adcfcadc8b7d117a095140a695702cbaeca412", "shasum": "" }, "type": "library", @@ -2413,7 +2441,7 @@ "php" ], "support": { - "source": "https://github.com/NativePHP/php-bin/tree/1.0.2" + "source": "https://github.com/NativePHP/php-bin/tree/1.0.3" }, "funding": [ { @@ -2425,7 +2453,7 @@ "type": "opencollective" } ], - "time": "2025-05-23T13:32:30+00:00" + "time": "2025-07-16T08:42:59+00:00" }, { "name": "nesbot/carbon", @@ -2682,16 +2710,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.5.0", + "version": "v5.6.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" + "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", - "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56", + "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56", "shasum": "" }, "require": { @@ -2734,9 +2762,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0" }, - "time": "2025-05-31T08:24:38+00:00" + "time": "2025-07-27T20:03:57+00:00" }, { "name": "nunomaduro/termwind", @@ -4058,16 +4086,16 @@ }, { "name": "spatie/laravel-package-tools", - "version": "1.92.4", + "version": "1.92.7", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "d20b1969f836d210459b78683d85c9cd5c5f508c" + "reference": "f09a799850b1ed765103a4f0b4355006360c49a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/d20b1969f836d210459b78683d85c9cd5c5f508c", - "reference": "d20b1969f836d210459b78683d85c9cd5c5f508c", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/f09a799850b1ed765103a4f0b4355006360c49a5", + "reference": "f09a799850b1ed765103a4f0b4355006360c49a5", "shasum": "" }, "require": { @@ -4107,7 +4135,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.92.4" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.92.7" }, "funding": [ { @@ -4115,7 +4143,7 @@ "type": "github" } ], - "time": "2025-04-11T15:27:14+00:00" + "time": "2025-07-17T15:46:43+00:00" }, { "name": "symfony/clock", @@ -7289,16 +7317,16 @@ }, { "name": "laravel/pint", - "version": "v1.23.0", + "version": "v1.24.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "9ab851dba4faa51a3c3223dd3d07044129021024" + "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/9ab851dba4faa51a3c3223dd3d07044129021024", - "reference": "9ab851dba4faa51a3c3223dd3d07044129021024", + "url": "https://api.github.com/repos/laravel/pint/zipball/0345f3b05f136801af8c339f9d16ef29e6b4df8a", + "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a", "shasum": "" }, "require": { @@ -7309,7 +7337,7 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.76.0", + "friendsofphp/php-cs-fixer": "^3.82.2", "illuminate/view": "^11.45.1", "larastan/larastan": "^3.5.0", "laravel-zero/framework": "^11.45.0", @@ -7354,20 +7382,20 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-07-03T10:37:47+00:00" + "time": "2025-07-10T18:09:32+00:00" }, { "name": "laravel/sail", - "version": "v1.43.1", + "version": "v1.44.0", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "3e7d899232a8c5e3ea4fc6dee7525ad583887e72" + "reference": "a09097bd2a8a38e23ac472fa6a6cf5b0d1c1d3fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/3e7d899232a8c5e3ea4fc6dee7525ad583887e72", - "reference": "3e7d899232a8c5e3ea4fc6dee7525ad583887e72", + "url": "https://api.github.com/repos/laravel/sail/zipball/a09097bd2a8a38e23ac472fa6a6cf5b0d1c1d3fe", + "reference": "a09097bd2a8a38e23ac472fa6a6cf5b0d1c1d3fe", "shasum": "" }, "require": { @@ -7417,7 +7445,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2025-05-19T13:19:21+00:00" + "time": "2025-07-04T16:17:06+00:00" }, { "name": "mockery/mockery", @@ -8354,16 +8382,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "2.1.0", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68" + "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", - "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/b9e61a61e39e02dd90944e9115241c7f7e76bfd8", + "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8", "shasum": "" }, "require": { @@ -8395,9 +8423,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.1.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.2.0" }, - "time": "2025-02-19T13:28:12+00:00" + "time": "2025-07-13T07:04:09+00:00" }, { "name": "phpunit/php-code-coverage", @@ -10009,7 +10037,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": { + "nativephp/electron": 20 + }, "prefer-stable": true, "prefer-lowest": false, "platform": { diff --git a/config/nativephp.php b/config/nativephp.php index 3d3e458..264ea6b 100644 --- a/config/nativephp.php +++ b/config/nativephp.php @@ -83,6 +83,7 @@ 'content', 'node_modules', '*/tests', + 'public/hot', // Remove Vite hot reload file in production // Don't exclude the build directory as it contains our native executable ], @@ -153,7 +154,8 @@ */ 'prebuild' => [ './build-swift-audio.sh', - 'npm run build', + 'npm install --omit=dev', // Install production dependencies + 'npm run build', // Build frontend assets 'php artisan optimize', 'php artisan config:cache', 'php artisan route:cache', diff --git a/database/factories/ConversationSessionFactory.php b/database/factories/ConversationSessionFactory.php index 4fd54d0..93e91c9 100644 --- a/database/factories/ConversationSessionFactory.php +++ b/database/factories/ConversationSessionFactory.php @@ -21,7 +21,7 @@ public function definition(): array { $startedAt = $this->faker->dateTimeBetween('-1 month', 'now'); $endedAt = $this->faker->optional(0.7)->dateTimeBetween($startedAt, 'now'); - + return [ 'user_id' => null, // Single-user desktop app 'title' => $this->faker->sentence(4), @@ -69,7 +69,7 @@ public function completed(): static return $this->state(function (array $attributes) { $startedAt = $attributes['started_at'] ?? now()->subHour(); $endedAt = now(); - + return [ 'ended_at' => $endedAt, 'duration_seconds' => $endedAt->diffInSeconds($startedAt), @@ -81,4 +81,4 @@ public function completed(): static ]; }); } -} \ No newline at end of file +} diff --git a/database/seeders/MeetingTemplateSeeder.php b/database/seeders/MeetingTemplateSeeder.php index c4d5395..4e8c29d 100644 --- a/database/seeders/MeetingTemplateSeeder.php +++ b/database/seeders/MeetingTemplateSeeder.php @@ -684,7 +684,13 @@ public function run(): void ]; foreach ($templates as $template) { - Template::create($template); + Template::firstOrCreate( + [ + 'name' => $template['name'], + 'is_system' => true + ], + $template + ); } } } diff --git a/electron-loop-audio.md b/electron-loop-audio.md new file mode 100644 index 0000000..245b4b0 --- /dev/null +++ b/electron-loop-audio.md @@ -0,0 +1,173 @@ +# Electron Audio Loopback + +An Electron plugin for capturing system audio loopback on macOS 12.3+, Windows 10+ and Linux without any third-party loopback drivers or dependencies. + +To play around with a full example, check out the [mic-speaker-streamer](https://github.com/alectrocute/mic-speaker-streamer) repo. It's a simple app that allows you to simultaneously stream your microphone and system audio to a third-party transcription API while also recording both streams into a WAV file. + +## Real-World Usage + +If your app is using Electron Audio Loopback, [make a PR](https://github.com/alectrocute/electron-audio-loopback/pulls) to add it to the list below! + +- [mic-speaker-streamer](https://github.com/alectrocute/mic-speaker-streamer): An example microphone/system audio transcription app using OpenAI's Realtime API. + +## Installation + +```bash +npm install electron-audio-loopback +``` + +## Usage + +### Main Process Setup + +```javascript +const { app } = require('electron'); +const { initMain } = require('electron-audio-loopback'); + +// Initialize this plugin in your main process +// before the app is ready. Simple! +initMain(); + +app.whenReady().then(() => { + // Your app initialization... +}); +``` + +### Renderer Process Usage + +#### Automatic Mode + +If `nodeIntegration` is enabled in your renderer process, then you can import the renderer helper function directly. This will take care of everything for you in one line of code. + +```javascript +const { getLoopbackAudioMediaStream } = require('electron-audio-loopback'); + +// Get a MediaStream with system audio loopback +const stream = await getLoopbackAudioMediaStream(); + +// The stream contains only audio tracks +const audioTracks = stream.getAudioTracks(); +console.log('Audio tracks:', audioTracks); + +// Use the stream with an audio element or Web Audio API +const audioElement = document.getElementById('audio'); +audioElement.srcObject = stream; +audioElement.play(); +``` + +If you don't want to remove the video tracks, you can pass `removeVideo: false` to the `getLoopbackAudioMediaStream` function. + +#### Manual Mode + +If you do not have `nodeIntegration` enabled in your renderer process, then you'll need to manually initialize the plugin via IPC. See the example below: + +```javascript +// preload.js +const { contextBridge, ipcRenderer } = require('electron'); + +contextBridge.exposeInMainWorld('electronAPI', { + enableLoopbackAudio: () => ipcRenderer.invoke('enable-loopback-audio'), + disableLoopbackAudio: () => ipcRenderer.invoke('disable-loopback-audio') +}); + +// renderer.js +async function getLoopbackAudioMediaStream() { + // Tell the main process to enable system audio loopback. + // This will override the default `getDisplayMedia` behavior. + await window.electronAPI.enableLoopbackAudio(); + + // Get a MediaStream with system audio loopback. + // `getDisplayMedia` will fail if you don't request `video: true`. + const stream = await navigator.mediaDevices.getDisplayMedia({ + video: true, + audio: true, + }); + + // Remove video tracks that we don't need. + // Note: You may find bugs if you don't remove video tracks. + const videoTracks = stream.getVideoTracks(); + + videoTracks.forEach(track => { + track.stop(); + stream.removeTrack(track); + }); + + // Tell the main process to disable system audio loopback. + // This will restore full `getDisplayMedia` functionality. + await window.electronAPI.disableLoopbackAudio(); + + // Boom! You've got a MediaStream with system audio loopback. + // Use it with an audio element or Web Audio API. + return stream; +} +``` + +## API Reference + +### Main Process Functions + +- `initMain(options?: InitMainOptions)`: Initialize the plugin in the main process. Must be called before the app is ready. + - `sourcesOptions`: The options to pass to the `desktopCapturer.getSources` method. + - `forceCoreAudioTap`: Whether to force the use of the Core Audio API on macOS (can be used to bypass bugs for certain macOS versions). + +### Renderer Process Functions + +- `getLoopbackAudioMediaStream(options?: GetLoopbackAudioMediaStreamOptions)`: Helper function that returns a Promise, resolves to a `MediaStream` containing system audio loopback. Video tracks are automatically removed from the stream. + - `removeVideo`: Whether to remove the video tracks from the stream. Defaults to `true`. + +### IPC Handlers + +The plugin registers these IPC handlers automatically, ensure you don't override them! + +- `enable-loopback-audio`: Enables system audio loopback capture +- `disable-loopback-audio`: Disables system audio loopback capture + +## Requirements + +- Electron >= 31.0.1 +- macOS 12.3+ +- Windows 10+ +- Most Linux distros + +## Development + +### Prerequisites + +- Node.js 18+ +- npm or yarn + +### Setup + +```bash +# Install dependencies +npm install + +# Build the project +npm run build + +# Development mode with watch +npm run dev + +# Lint code +npm run lint + +# Run example +npm test +``` + +PR's welcome! + +### Project Structure + +```bash +src/ +├── index.ts # Main entry point with conditional exports +├── main.ts # Main process initialization +├── config.ts # Configuration +├── types.d.ts # Type definitions +└── renderer.ts # Renderer process helper function +``` + +## License + +MIT © Alec Armbruster [@alectrocute](https://github.com/alectrocute) \ No newline at end of file diff --git a/node-mac-permissions.md b/node-mac-permissions.md new file mode 100644 index 0000000..0e7819f --- /dev/null +++ b/node-mac-permissions.md @@ -0,0 +1,555 @@ +[![MIT license](https://img.shields.io/badge/License-MIT-blue.svg)](https://lbesson.mit-license.org/) + [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![Actions Status](https://github.com/codebytere/node-mac-permissions/workflows/Test/badge.svg)](https://github.com/codebytere/node-mac-permissions/actions) + +# node-mac-permissions + +## Table of Contents + +- [Overview](#overview) +- [API](#api) + - [`permissions.getAuthStatus(type)`](#permissionsgetauthstatustype) + - [`permissions.askForAccessibilityAccess()`](#permissionsaskforaccessibilityaccess) + - [`permissions.askForAppleEventsAccess()`](#permissionsaskforappleeventsaccesstargetappbundleid) + - [`permissions.askForContactsAccess()`](#permissionsaskforcontactsaccess) + - [`permissions.askForCalendarAccess([accessLevel])`](#permissionsaskforcalendaraccessaccesslevel) + - [`permissions.askForSpeechRecognitionAccess()`](#permissionsaskforspeechrecognitionaccess) + - [`permissions.askForRemindersAccess()`](#permissionsaskforremindersaccess) + - [`permissions.askForFoldersAccess(folder)`](#permissionsaskforfoldersaccessfolder) + - [`permissions.askForFullDiskAccess()`](#permissionsaskforfulldiskaccess) + - [`permissions.askForCameraAccess()`](#permissionsaskforcameraaccess) + - [`permissions.askForLocationAccess([accessLevel])](#permissionsaskforlocationaccessaccesslevel) + - [`permissions.askForInputMonitoringAccess([accessLevel])`](#permissionsaskforinputmonitoringaccessaccesslevel) + - [`permissions.askForMicrophoneAccess()`](#permissionsaskformicrophoneaccess) + - [`permissions.askForMusicLibraryAccess()`](#permissionsaskformusiclibraryaccess) + - [`permissions.askForPhotosAccess([accessLevel])`](#permissionsaskforphotosaccessaccesslevel) + - [`permissions.askForScreenCaptureAccess([openPreferences])`](#permissionsaskforscreencaptureaccessopenpreferences) +- [FAQ](#faq) + +## Overview + +```js +npm i node-mac-permissions +``` + +This native Node.js module allows you to manage an app's access to: + +- Apple Events +- Accessibility +- Calendar +- Camera +- Contacts +- Full Disk Access +- Input Monitoring +- Location +- Microphone +- Photos +- Protected Folders +- Reminders +- Screen Capture +- Speech Recognition +- Notifications + +If you need to ask for permissions, your app must be allowed to ask for permission : + +- For a Nodejs script/app, you can use a terminal app such as [iTerm2](https://iterm2.com/) (it won't work on macOS Terminal.app) +- For an Electron app (or equivalent), you'll have to update `Info.plist` to include a usage description key like `NSMicrophoneUsageDescription` for microphone permission. + +If you're using macOS 12.3 or newer, you'll need to ensure you have Python installed on your system, as macOS does not bundle it anymore. + +## API + +### `permissions.getAuthStatus(type)` + +- `type` String - The type of system component to which you are requesting access. Can be one of `accessibility`, `bluetooth`, `calendar`, `camera`, `contacts`, `full-disk-access`, `input-monitoring`, `location`, `microphone`,`notifications`, `photos`, `reminders`, `screen`, or `speech-recognition`. + +Returns `String` - Can be one of `not determined`, `denied`, `authorized`, `limited`, `provisional`, or `restricted`. + +Checks the authorization status of the application to access `type` on macOS. + +Return Value Descriptions: + +- `not determined` - The user has not yet made a choice regarding whether the application may access `type` data. +- `restricted` - The application is not authorized to access `type` data. The user cannot change this application’s status, possibly due to active restrictions such as parental controls being in place. +- `denied` - The user explicitly denied access to `type` data for the application. +- `authorized` - The application is authorized to access `type` data. +- `limited` - The application is authorized for limited access to `type` data. Currently only applicable to the `photos` type. +- 'provisional' - The application is provisionally authorized to access `type` data. Currently only applicable to the `notifications` type. + +**Notes:** + +- Access to `bluetooth` will always return a status of `authorized` prior to macOS 10.15, as the underlying API was not introduced until that version. +- Access to `camera` and `microphone` will always return a status of `authorized` prior to macOS 10.14, as the underlying API was not introduced until that version. +- Access to `input-monitoring` will always return a status of `authorized` prior to macOS 10.15, as the underlying API was not introduced until that version. +- Access to `music-library` will always return a status of `authorized` prior to macOS 11.0, as the underlying API was not introduced until that version. +- Access to `screen` will always return a status of `authorized` prior to macOS 10.15, as the underlying API was not introduced until that version. +- Access to `speech-recognition` will always return a status of `authorized` prior to macOS 10.15, as the underlying API was not introduced until that version. + +Example: + +```js +const types = [ + 'accessibility', + 'bluetooth', + 'calendar', + 'camera', + 'contacts', + 'full-disk-access', + 'input-monitoring', + 'location', + 'microphone', + 'music-library', + 'notifications', + 'photos-add-only', + 'photos-read-write', + 'reminders', + 'speech-recognition', + 'screen', +] + +for (const type of types) { + const status = getAuthStatus(type) + console.log(`Access to ${type} is ${status}`) +} +``` + +### `permissions.askForAccessibilityAccess()` + +There is no API for programmatically requesting Accessibility access on macOS at this time, and so calling this method will trigger opening of System Preferences at the Accessibility pane of Security and Privacy. + +Example: + +```js +const { askForAccessibilityAccess } = require('node-mac-permissions') + +askForAccessibilityAccess() +``` + +### `permissions.askForAppleEventsAccess(targetAppBundleId[, shouldPrompt])` + +- `targetAppBundleId` String - The bundle identifier for the app you're targeting to send Apple Events to. +- `shouldPrompt` Boolean (optional) - If this is `false`, the current authorization status will be returned for the provided `targetAppBundleId`. Default is true. + +Returns `Promise` - Whether or not the request succeeded or failed; can be `authorized`, `denied`, or `not determined`. + +Your app’s `Info.plist` file must provide a value for the `NSAppleEventsUsageDescription` key that explains to the user why your app is requesting the ability to send Apple Events. + +```plist +NSAppleEventsUsageDescription +Your reason for wanting the ability to send Apple Events +``` + +Example: + +```js +const { askForAppleEventsAccess } = require('node-mac-permissions') + +askForAppleEventsAccess('com.apple.finder').then(status => { + console.log(`Access to send Apple Events is ${status}`) +}) +``` + +### `permissions.askForContactsAccess()` + +Returns `Promise` - Whether or not the request succeeded or failed; can be `authorized` or `denied`. + +Your app’s `Info.plist` file must provide a value for the `NSContactsUsageDescription` key that explains to the user why your app is requesting Contacts access. + +```plist +NSContactsUsageDescription +Your reason for wanting to access the Contact store +``` + +Example: + +```js +const { askForContactsAccess } = require('node-mac-permissions') + +askForContactsAccess().then(status => { + console.log(`Access to Contacts is ${status}`) +}) +``` + +### `permissions.askForCalendarAccess([accessLevel])` + +- `accessLevel` String (optional) - The access level being requested of Photos. Can be either `write-only` or `full`. Default is `write-only`. Only available on macOS 14 or higher. + +Returns `Promise` - Whether or not the request succeeded or failed; can be `authorized` or `denied`. + +Example: + +```js +const { askForCalendarAccess } = require('node-mac-permissions') + +askForCalendarAccess().then(status => { + console.log(`Access to Calendar is ${status}`) +}) +``` + +On macOS 14 and newer, your app’s `Info.plist` file must provide a value for either the `NSCalendarsWriteOnlyAccessUsageDescription` key or the `NSCalendarsFullAccessUsageDescription` key that explains to the user why your app is requesting Calendar access. + +```plist +NSCalendarsWriteOnlyAccessUsageDescription +Your reason for wanting write-only Calendar access +``` + +```plist +NSCalendarsFullAccessUsageDescription +Your reason for wanting full Calendar access +``` + +### `permissions.askForSpeechRecognitionAccess()` + +Returns `Promise` - Whether or not the request succeeded or failed; can be `authorized`, `denied`, or `restricted`. + +Checks the authorization status for Speech Recognition access. If the status check returns: + +- `not determined` - The Speech Recognition access authorization will prompt the user to authorize or deny. The Promise is resolved after the user selection with either `authorized` or `denied`. +- `denied` - The `Security & Privacy` System Preferences window is opened with the Speech Recognition privacy key highlighted. On open of the `Security & Privacy` window, the Promise is resolved as `denied`. +- `restricted` - The Promise is resolved as `restricted`. + +Your app must provide an explanation for its use of Speech Recognition using the `NSSpeechRecognitionUsageDescription` `Info.plist` key; + +```plist +NSSpeechRecognitionUsageDescription +Your reason for wanting to access Speech Recognition +``` + +Example: + +```js +const { askForSpeechRecognitionAccess } = require('node-mac-permissions') + +askForSpeechRecognitionAccess().then(status => { + console.log(`Access to Speech Recognition is ${status}`) +}) +``` + +**Note:** `status` will be resolved back as `authorized` prior to macOS 10.15, as the underlying API was not introduced until that version. + +### `permissions.askForRemindersAccess()` + +Returns `Promise` - Whether or not the request succeeded or failed; can be `authorized` or `denied`. + +Example: + +```js +const { askForRemindersAccess } = require('node-mac-permissions') + +askForRemindersAccess().then(status => { + console.log(`Access to Reminders is ${status}`) +}) +``` + +On macOS 14 and newer, your app’s `Info.plist` file must provide a value for the `NSRemindersFullAccessUsageDescription` key that explains to the user why your app is +requesting Reminders access. + +```plist +NSRemindersFullAccessUsageDescription +Your reason for wanting access to read and write Reminders data. +``` + +### `permissions.askForFoldersAccess(folder)` + +- `type` String - The folder to which you are requesting access. Can be one of `desktop`, `documents`, or `downloads`. + +Returns `Promise` - Whether or not the request succeeded or failed; can be `authorized` or `denied`. + +Example: + +```js +const { askForFoldersAccess } = require('node-mac-permissions') + +askForFoldersAccess('desktop').then(status => { + console.log(`Access to Desktop is ${status}`) +}) +``` + +```plist +NSDesktopFolderUsageDescription +Your reason for wanting to access the Desktop folder +``` + +```plist +NSDocumentsFolderUsageDescription +Your reason for wanting to access the Documents folder +``` + +```plist +NSDownloadsFolderUsageDescription +Your reason for wanting to access the Downloads folder +``` + +### `permissions.askForFullDiskAccess()` + +There is no API for programmatically requesting Full Disk Access on macOS at this time, and so calling this method will trigger opening of System Preferences at the Full Disk pane of Security and Privacy. + +Example: + +```js +const { askForFullDiskAccess } = require('node-mac-permissions') + +askForFullDiskAccess() +``` + +If you would like your app to pop up a dialog requesting full disk access when your app attempts to access protected resources, you should add the `NSSystemAdministrationUsageDescription` key to your `Info.plist`: + +```plist +NSSystemAdministrationUsageDescription +Your reason for wanting Full Disk Access +``` + +### `permissions.askForCameraAccess()` + +Returns `Promise` - Current permission status; can be `authorized`, `denied`, or `restricted`. + +Checks the authorization status for camera access. If the status check returns: + +- `not determined` - The camera access authorization will prompt the user to authorize or deny. The Promise is resolved after the user selection with either `authorized` or `denied`. +- `denied` - The `Security & Privacy` System Preferences window is opened with the Camera privacy key highlighted. On open of the `Security & Privacy` window, the Promise is resolved as `denied`. +- `restricted` - The Promise is resolved as `restricted`. + +Your app must provide an explanation for its use of capture devices using the `NSCameraUsageDescription` `Info.plist` key; Calling this method or attempting to start a capture session without a usage description raises an exception. + +```plist +NSCameraUsageDescription +Your reason for wanting to access the Camera +``` + +**Note:** + +- `status` will be resolved back as `authorized` prior to macOS 10.14, as the underlying API was not introduced until that version. + +Example: + +```js +const { askForCameraAccess } = require('node-mac-permissions') + +askForCameraAccess().then(status => { + console.log(`Access to Camera is ${status}`) +}) +``` + +### `permissions.askForLocationAccess([accessLevel])` + +- `accessLevel` String (optional) - The access level being requested of Location. Can be either `always` or `when-in-use`. Default is `when-in-use`. Only available on macOS 10.15 or higher. + +Returns `void`. + +Checks the authorization status for input monitoring access. If the status check returns: + +- `not determined` - A dialog will be displayed directing the user to the `Locatiojn` System Preferences window , where the user can approve your app to access location events in the background. +- `denied` - The `Location` System Preferences window is opened with the Location privacy key highlighted. + +Your app must provide an explanation for its use of location using either the `NSLocationUsageDescription` `Info.plist` key; Calling this method or attempting to access location without a usage description raises an exception. + +```plist +NSLocationUsageDescription +Your reason for wanting to access the user's Location +``` + +### `permissions.askForInputMonitoringAccess([accessLevel])` + +- `accessLevel` String (optional) - The access level being requested of Input Monitoring. Can be either `post` or `listen`. Default is `listen` Only available on macOS 10.15 or higher. + +Returns `Promise` - Current permission status; can be `authorized` or `denied`. + +Checks the authorization status for input monitoring access. If the status check returns: + +- `not determined` - A dialog will be displayed directing the user to the `Security & Privacy` System Preferences window , where the user can approve your app to monitor keyboard events in the background. The Promise is resolved as `denied`. +- `denied` - The `Security & Privacy` System Preferences window is opened with the Input Monitoring privacy key highlighted. On open of the `Security & Privacy` window, the Promise is resolved as `denied`. + +**Note:** + +- `status` will be resolved back as `authorized` prior to macOS 10.15, as the underlying API was not introduced until that version. + +Example: + +```js +const { askForInputMonitoringAccess } = require('node-mac-permissions') + +askForInputMonitoringAccess().then(status => { + console.log(`Access to Input Monitoring is ${status}`) +}) +``` + +### `permissions.askForMicrophoneAccess()` + +Returns `Promise` - Current permission status; can be `authorized`, `denied`, or `restricted`. + +Checks the authorization status for microphone access. If the status check returns: + +- `not determined` - The microphone access authorization will prompt the user to authorize or deny. The Promise is resolved after the user selection with either `authorized` or `denied`. +- `denied` - The `Security & Privacy` System Preferences window is opened with the Microphone privacy key highlighted. On open of the `Security & Privacy` window, the Promise is resolved as `denied`. +- `restricted` - The Promise is resolved as `restricted`. + +Your app must provide an explanation for its use of capture devices using the `NSMicrophoneUsageDescription` `Info.plist` key; Calling this method or attempting to start a capture session without a usage description raises an exception. + +```plist +NSMicrophoneUsageDescription +Your reason for wanting to access the Microphone +``` + +**Note:** + +- `status` will be resolved back as `authorized` prior to macOS 10.14, as the underlying API was not introduced until that version. + +Example: + +```js +const { askForMicrophoneAccess } = require('node-mac-permissions') + +askForMicrophoneAccess().then(status => { + console.log(`Access to Microphone is ${status}`) +}) +``` + +### `permissions.askForMusicLibraryAccess()` + +Returns `Promise` - Whether or not the request succeeded or failed; can be `authorized`, `denied`, or `restricted`. + +- `not determined` - The Music Library access authorization will prompt the user to authorize or deny. The Promise is resolved after the user selection with either `authorized` or `denied`. +- `denied` - The `Security & Privacy` System Preferences window is opened with the Music Library privacy key highlighted. On open of the `Security & Privacy` window, the Promise is resolved as `denied`. +- `restricted` - The Promise is resolved as `restricted`. + +Your app must provide an explanation for its use of the music library using the `NSAppleMusicUsageDescription` `Info.plist` key. + +```plist +NSAppleMusicUsageDescription +Your reason for wanting to access the user’s media library. +``` + +**Note:** + +- `status` will be resolved back as `authorized` prior to macOS 11.0, as the underlying API was not introduced until that version. + +Example: + +```js +const { askForMusicLibraryAccess } = require('node-mac-permissions') + +askForMusicLibraryAccess().then(status => { + console.log(`Access to Apple Music Library is ${status}`) +}) +``` + +### `permissions.askForPhotosAccess([accessLevel])` + +- `accessLevel` String (optional) - The access level being requested of Photos. Can be either `add-only` or `read-write`. Default is `add-only`. Only available on macOS 11 or higher. + +Returns `Promise` - Current permission status; can be `authorized`, `denied`, or `restricted`. + +Checks the authorization status for Photos access. If the status check returns: + +- `not determined` - The Photos access authorization will prompt the user to authorize or deny. The Promise is resolved after the user selection with either `authorized` or `denied`. +- `denied` - The `Security & Privacy` System Preferences window is opened with the Photos privacy key highlighted. On open of the `Security & Privacy` window, the Promise is resolved as `denied`. +- `restricted` - The Promise is resolved as `restricted`. + +Your app must provide an explanation for its use of the photo library using either the `NSPhotoLibraryUsageDescription` or the `NSPhotoLibraryAddUsageDescription` `Info.plist` key. + +For requesting add-only access to the user’s photo library: + +```plist +NSPhotoLibraryAddUsageDescription +Your reason for wanting to access Photos +``` + +For requesting read/write access to the user’s photo library: + +```plist +NSPhotoLibraryUsageDescription +Your reason for wanting to access Photos +``` + +**Note:** + +You should add the `PHPhotoLibraryPreventAutomaticLimitedAccessAlert` key with a Boolean value of `YES` to your app’s `Info.plist` file to prevent the system from automatically presenting the limited library selection prompt. See [`PHAuthorizationStatusLimited`](https://developer.apple.com/documentation/photokit/phauthorizationstatus/phauthorizationstatuslimited?language=objc) for more information. + +Example: + +```js +const { askForPhotosAccess } = require('node-mac-permissions') + +askForPhotosAccess().then(status => { + console.log(`Access to Photos is ${status}`) +}) +``` + +### `permissions.askForScreenCaptureAccess([openPreferences])` + +- `openPreferences` Boolean (optional) - Whether to open System Preferences if the request to authorize Screen Capture fails or is denied by the user. + +Calling this method for the first time within an app session will trigger a permission modal. If this modal is denied, there is no API for programmatically requesting Screen Capture on macOS at this time. Calling this method after denial with `openPreferences = true` will trigger opening of System Preferences at the Screen Capture pane of Security and Privacy. + +Example: + +```js +const { askForScreenCaptureAccess } = require('node-mac-permissions') + +askForScreenCaptureAccess() +``` + +## FAQ + +Q. I'm seeing an error like the following when using webpack: + +```sh +App threw an error during load +TypeError: Cannot read property 'indexOf' of undefined + at Function.getFileName (webpack-internal:///./node_modules/bindings/bindings.js:178:16) +``` + +A. This error means that webpack packed this module, which it should not. To fix this, you should configure webpack to use this module externally, e.g explicitly not pack it. + +---------------------- + +Q. I've authorized access to a particular system component and want to reset it. How do I do that? + +A. You can use `tccutil` to do this! + +The `tccutil` command manages the privacy database, which stores decisions the user has made about whether apps may access personal data. + +Examples: + +```sh +# Reset all app permissions +$ tccutil reset All + +# Reset Accessibility access permissions +$ tccutil reset Accessibility + +# Reset Reminders access permissions +$ tccutil reset Reminders + +# Reset Calendar access permissions +$ tccutil reset Calendar + +# Reset Camera access permissions +$ tccutil reset Camera + +# Reset Microphone access permissions +$ tccutil reset Microphone + +# Reset Photos access permissions +$ tccutil reset Photos + +# Reset Screen Capture access permissions +$ tccutil reset ScreenCapture + +# Reset Full Disk Access permissions +$ tccutil reset SystemPolicyAllFiles + +# Reset Contacts permissions +$ tccutil reset AddressBook + +# Reset Desktop folder access +$ tccutil reset SystemPolicyDesktopFolder + +# Reset Documents folder access +$ tccutil reset SystemPolicyDocumentsFolder + +# Reset Downloads folder access +$ tccutil reset SystemPolicyDownloadsFolder + +# Reset Removable Volumes access +$ tccutil reset SystemPolicyRemovableVolumes +``` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index fcfd94e..8f77284 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "1.0.0", "dependencies": { "@inertiajs/vue3": "^2.0.0", + "@openai/agents": "^0.0.12", + "@openai/agents-realtime": "^0.0.12", "@tailwindcss/vite": "^4.1.1", "@types/dompurify": "^3.0.5", "@vitejs/plugin-vue": "^5.2.1", @@ -18,9 +20,11 @@ "concurrently": "^9.0.1", "date-fns": "^4.1.0", "dompurify": "^3.2.6", + "electron-audio-loopback": "^1.0.5", "laravel-vite-plugin": "^1.0", "lucide-vue-next": "^0.468.0", "marked": "^16.0.0", + "pinia": "^3.0.3", "reka-ui": "^2.2.0", "tailwind-merge": "^3.2.0", "tailwindcss": "^4.1.1", @@ -46,7 +50,8 @@ "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "4.9.5", "@tailwindcss/oxide-linux-x64-gnu": "^4.0.1", - "lightningcss-linux-x64-gnu": "^1.29.1" + "lightningcss-linux-x64-gnu": "^1.29.1", + "node-mac-permissions": "^2.5.0" } }, "node_modules/@ampproject/remapping": { @@ -81,12 +86,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.28.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -96,9 +101,9 @@ } }, "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -108,10 +113,42 @@ "node": ">=6.9.0" } }, + "node_modules/@electron/get": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", + "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@electron/get/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", - "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", "cpu": [ "ppc64" ], @@ -125,9 +162,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", - "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", "cpu": [ "arm" ], @@ -141,9 +178,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", - "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", "cpu": [ "arm64" ], @@ -157,9 +194,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", - "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", "cpu": [ "x64" ], @@ -173,9 +210,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", - "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", "cpu": [ "arm64" ], @@ -189,9 +226,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", - "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", "cpu": [ "x64" ], @@ -205,9 +242,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", - "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", "cpu": [ "arm64" ], @@ -221,9 +258,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", - "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", "cpu": [ "x64" ], @@ -237,9 +274,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", - "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", "cpu": [ "arm" ], @@ -253,9 +290,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", - "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", "cpu": [ "arm64" ], @@ -269,9 +306,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", - "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", "cpu": [ "ia32" ], @@ -285,9 +322,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", - "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", "cpu": [ "loong64" ], @@ -301,9 +338,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", - "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", "cpu": [ "mips64el" ], @@ -317,9 +354,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", - "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", "cpu": [ "ppc64" ], @@ -333,9 +370,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", - "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", "cpu": [ "riscv64" ], @@ -349,9 +386,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", - "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", "cpu": [ "s390x" ], @@ -365,9 +402,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", - "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", "cpu": [ "x64" ], @@ -381,9 +418,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", - "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", "cpu": [ "arm64" ], @@ -397,9 +434,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", - "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", "cpu": [ "x64" ], @@ -413,9 +450,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", - "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", "cpu": [ "arm64" ], @@ -429,9 +466,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", - "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", "cpu": [ "x64" ], @@ -444,10 +481,26 @@ "node": ">=18" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", - "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", "cpu": [ "x64" ], @@ -461,9 +514,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", - "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", "cpu": [ "arm64" ], @@ -477,9 +530,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", - "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", "cpu": [ "ia32" ], @@ -493,9 +546,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", - "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", "cpu": [ "x64" ], @@ -538,9 +591,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -553,9 +606,9 @@ } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -577,9 +630,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", - "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -587,9 +640,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", - "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -624,9 +677,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -648,9 +701,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", - "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz", + "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==", "dev": true, "license": "MIT", "engines": { @@ -671,13 +724,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", - "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.14.0", + "@eslint/core": "^0.15.1", "levn": "^0.4.1" }, "engines": { @@ -685,38 +738,38 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.1.tgz", - "integrity": "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.9" + "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.1.tgz", - "integrity": "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.3.tgz", + "integrity": "sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.1", - "@floating-ui/utils": "^0.2.9" + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/utils": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", - "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", "license": "MIT" }, "node_modules/@floating-ui/vue": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.6.tgz", - "integrity": "sha512-XFlUzGHGv12zbgHNk5FN2mUB7ROul3oG2ENdTpWdE+qMFxyNxWSRmsoyhiEnpmabNm6WnUvR1OvJfUfN4ojC1A==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.8.tgz", + "integrity": "sha512-SNJAa1jbT8Gh1LvWw2uIIViLL0saV2bCY59ISCvJzhbut5DSb2H3LKUK49Xkd7SixTNHKX4LFu59nbwIXt9jjQ==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.0.0", - "@floating-ui/utils": "^0.2.9", + "@floating-ui/dom": "^1.7.3", + "@floating-ui/utils": "^0.2.10", "vue-demi": ">=0.13.0" } }, @@ -813,9 +866,9 @@ } }, "node_modules/@inertiajs/core": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-2.0.11.tgz", - "integrity": "sha512-DSFdkPLvLwHC0hePc/692WK9ItRzddVZ+OS5ZO1B2+6TmZnZH3kTQZGEhaYAtkAB+DHKxOm1oGHPKQrsAZ54qQ==", + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-2.0.17.tgz", + "integrity": "sha512-tvYoqiouQSJrP7i7zVq61yyuEjlL96UU4nkkOWtOajXZlubGN4XrgRpnygpDk1KBO8V2yBab3oUZm+aZImwTHg==", "license": "MIT", "dependencies": { "axios": "^1.8.2", @@ -824,12 +877,12 @@ } }, "node_modules/@inertiajs/vue3": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@inertiajs/vue3/-/vue3-2.0.11.tgz", - "integrity": "sha512-dlx62L7heOzQzBwRl7q/aZyS/b3AcwO2x6VMPfcSkCn+yA/HEImUJ6AlVojrl/vyvNf9PelermyQ7aWJNHdvqw==", + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@inertiajs/vue3/-/vue3-2.0.17.tgz", + "integrity": "sha512-Al0IMHQSj5aTQBLUAkljFEMCw4YRwSiOSKzN8LAbvJpKwvJFgc/wSj3wVVpr/AO9y9mz1w2mtvjnDoOzsntPLw==", "license": "MIT", "dependencies": { - "@inertiajs/core": "2.0.11", + "@inertiajs/core": "2.0.17", "es-toolkit": "^1.33.0" }, "peerDependencies": { @@ -846,9 +899,9 @@ } }, "node_modules/@internationalized/number": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.3.tgz", - "integrity": "sha512-p+Zh1sb6EfrfVaS86jlHGQ9HA66fJhV9x5LiE5vCbZtXEHAuhcmUZUdZ4WrFpUBfNalr2OkAJI5AcKEQF+Lebw==", + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.4.tgz", + "integrity": "sha512-P+/h+RDaiX8EGt3shB9AYM1+QgkvHmJ5rKi4/59k4sg9g58k9rqsRW0WxRO7jCoHyvVbFRRFKmVTdFYdehrxHg==", "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" @@ -867,17 +920,13 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -889,31 +938,46 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.1.tgz", + "integrity": "sha512-CPle1OQehbWqd25La9Ack5B07StKIxh4+Bf19qnpZKJC1oI22Y0czZHbifjw1UoczIfKBwBDAp/dFxvHG13B5A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ajv": "^6.12.6", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -952,10 +1016,80 @@ "node": ">= 8" } }, + "node_modules/@openai/agents": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@openai/agents/-/agents-0.0.12.tgz", + "integrity": "sha512-36DBV9Z7zST2Do4Wcfeb/ku0HxMVXvRsHAoTwrEZlLl3tdJcVqzhjNf+ZBumFG1vpflHveCE8iuR/KTGmGoC6A==", + "license": "MIT", + "dependencies": { + "@openai/agents-core": "0.0.12", + "@openai/agents-openai": "0.0.12", + "@openai/agents-realtime": "0.0.12", + "debug": "^4.4.0", + "openai": "^5.10.1" + } + }, + "node_modules/@openai/agents-core": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@openai/agents-core/-/agents-core-0.0.12.tgz", + "integrity": "sha512-GZ3k+QvehH794OrWmXlHcLNX4OnYAQxd4zg7M3Dfk40Rhxl0p0v3CUjevLqO1Hp3rsfstbx8vGR9Qpmvmzuv3A==", + "license": "MIT", + "dependencies": { + "@openai/zod": "npm:zod@3.25.40 - 3.25.67", + "debug": "^4.4.0", + "openai": "^5.10.1" + }, + "optionalDependencies": { + "@modelcontextprotocol/sdk": "^1.12.0" + }, + "peerDependencies": { + "zod": "3.25.40 - 3.25.67" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@openai/agents-openai": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@openai/agents-openai/-/agents-openai-0.0.12.tgz", + "integrity": "sha512-frp3eJ4UwOpfX9gVSyTtbTif4e4oo+/SADpTLcG9HMv2ubEXcZlClngtqTQ97p7cmk9Pqvl5GLnQpq2tX36HZQ==", + "license": "MIT", + "dependencies": { + "@openai/agents-core": "0.0.12", + "@openai/zod": "npm:zod@3.25.40 - 3.25.67", + "debug": "^4.4.0", + "openai": "^5.10.1" + } + }, + "node_modules/@openai/agents-realtime": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@openai/agents-realtime/-/agents-realtime-0.0.12.tgz", + "integrity": "sha512-P76qomYpNkHJ3vjzWhqhh4+m1REHhjy2ty7IglKBCf2QMk9k4CGFJw75YDffEVD/Z/8eT+ouiL+pho/6cAFCQg==", + "license": "MIT", + "dependencies": { + "@openai/agents-core": "0.0.12", + "@openai/zod": "npm:zod@3.25.40 - 3.25.67", + "@types/ws": "^8.18.1", + "debug": "^4.4.0", + "ws": "^8.18.1" + } + }, + "node_modules/@openai/zod": { + "name": "zod", + "version": "3.25.67", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", + "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz", - "integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", + "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", "cpu": [ "arm" ], @@ -966,9 +1100,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz", - "integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", + "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", "cpu": [ "arm64" ], @@ -979,9 +1113,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz", - "integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", + "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", "cpu": [ "arm64" ], @@ -992,9 +1126,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz", - "integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", + "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", "cpu": [ "x64" ], @@ -1005,9 +1139,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz", - "integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", + "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", "cpu": [ "arm64" ], @@ -1018,9 +1152,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz", - "integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", + "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", "cpu": [ "x64" ], @@ -1031,9 +1165,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz", - "integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", + "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", "cpu": [ "arm" ], @@ -1044,9 +1178,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz", - "integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", + "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", "cpu": [ "arm" ], @@ -1057,9 +1191,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz", - "integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", + "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", "cpu": [ "arm64" ], @@ -1070,9 +1204,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz", - "integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", + "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", "cpu": [ "arm64" ], @@ -1083,9 +1217,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz", - "integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", + "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", "cpu": [ "loong64" ], @@ -1095,10 +1229,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz", - "integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", + "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", "cpu": [ "ppc64" ], @@ -1109,9 +1243,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz", - "integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", + "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", "cpu": [ "riscv64" ], @@ -1122,9 +1256,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz", - "integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", + "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", "cpu": [ "riscv64" ], @@ -1135,9 +1269,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz", - "integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", + "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", "cpu": [ "s390x" ], @@ -1161,9 +1295,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz", - "integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", + "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", "cpu": [ "x64" ], @@ -1174,9 +1308,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz", - "integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", + "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", "cpu": [ "arm64" ], @@ -1187,9 +1321,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz", - "integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", + "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", "cpu": [ "ia32" ], @@ -1200,9 +1334,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz", - "integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", + "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", "cpu": [ "x64" ], @@ -1212,6 +1346,19 @@ "win32" ] }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/@swc/helpers": { "version": "0.5.17", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", @@ -1221,10 +1368,23 @@ "tslib": "^2.8.0" } }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "license": "MIT", + "peer": true, + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@tailwindcss/node": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.8.tgz", - "integrity": "sha512-OWwBsbC9BFAJelmnNcrKuf+bka2ZxCE2A4Ft53Tkg4uoiE67r/PMEYwCsourC26E+kmxfwE0hVzMdxqeW+xu7Q==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", + "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", @@ -1233,13 +1393,13 @@ "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", - "tailwindcss": "4.1.8" + "tailwindcss": "4.1.11" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.8.tgz", - "integrity": "sha512-d7qvv9PsM5N3VNKhwVUhpK6r4h9wtLkJ6lz9ZY9aeZgrUWk1Z8VPyqyDT9MZlem7GTGseRQHkeB1j3tC7W1P+A==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", + "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -1250,24 +1410,24 @@ "node": ">= 10" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.8", - "@tailwindcss/oxide-darwin-arm64": "4.1.8", - "@tailwindcss/oxide-darwin-x64": "4.1.8", - "@tailwindcss/oxide-freebsd-x64": "4.1.8", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.8", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.8", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.8", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.8", - "@tailwindcss/oxide-linux-x64-musl": "4.1.8", - "@tailwindcss/oxide-wasm32-wasi": "4.1.8", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.8", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.8" + "@tailwindcss/oxide-android-arm64": "4.1.11", + "@tailwindcss/oxide-darwin-arm64": "4.1.11", + "@tailwindcss/oxide-darwin-x64": "4.1.11", + "@tailwindcss/oxide-freebsd-x64": "4.1.11", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", + "@tailwindcss/oxide-linux-x64-musl": "4.1.11", + "@tailwindcss/oxide-wasm32-wasi": "4.1.11", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.8.tgz", - "integrity": "sha512-Fbz7qni62uKYceWYvUjRqhGfZKwhZDQhlrJKGtnZfuNtHFqa8wmr+Wn74CTWERiW2hn3mN5gTpOoxWKk0jRxjg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", + "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", "cpu": [ "arm64" ], @@ -1281,9 +1441,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.8.tgz", - "integrity": "sha512-RdRvedGsT0vwVVDztvyXhKpsU2ark/BjgG0huo4+2BluxdXo8NDgzl77qh0T1nUxmM11eXwR8jA39ibvSTbi7A==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", + "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", "cpu": [ "arm64" ], @@ -1297,9 +1457,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.8.tgz", - "integrity": "sha512-t6PgxjEMLp5Ovf7uMb2OFmb3kqzVTPPakWpBIFzppk4JE4ix0yEtbtSjPbU8+PZETpaYMtXvss2Sdkx8Vs4XRw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", + "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", "cpu": [ "x64" ], @@ -1313,9 +1473,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.8.tgz", - "integrity": "sha512-g8C8eGEyhHTqwPStSwZNSrOlyx0bhK/V/+zX0Y+n7DoRUzyS8eMbVshVOLJTDDC+Qn9IJnilYbIKzpB9n4aBsg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", + "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", "cpu": [ "x64" ], @@ -1329,9 +1489,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.8.tgz", - "integrity": "sha512-Jmzr3FA4S2tHhaC6yCjac3rGf7hG9R6Gf2z9i9JFcuyy0u79HfQsh/thifbYTF2ic82KJovKKkIB6Z9TdNhCXQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", + "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", "cpu": [ "arm" ], @@ -1345,9 +1505,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.8.tgz", - "integrity": "sha512-qq7jXtO1+UEtCmCeBBIRDrPFIVI4ilEQ97qgBGdwXAARrUqSn/L9fUrkb1XP/mvVtoVeR2bt/0L77xx53bPZ/Q==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", + "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", "cpu": [ "arm64" ], @@ -1361,9 +1521,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.8.tgz", - "integrity": "sha512-O6b8QesPbJCRshsNApsOIpzKt3ztG35gfX9tEf4arD7mwNinsoCKxkj8TgEE0YRjmjtO3r9FlJnT/ENd9EVefQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", + "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", "cpu": [ "arm64" ], @@ -1377,9 +1537,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.8.tgz", - "integrity": "sha512-32iEXX/pXwikshNOGnERAFwFSfiltmijMIAbUhnNyjFr3tmWmMJWQKU2vNcFX0DACSXJ3ZWcSkzNbaKTdngH6g==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", + "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", "cpu": [ "x64" ], @@ -1393,9 +1553,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.8.tgz", - "integrity": "sha512-s+VSSD+TfZeMEsCaFaHTaY5YNj3Dri8rST09gMvYQKwPphacRG7wbuQ5ZJMIJXN/puxPcg/nU+ucvWguPpvBDg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", + "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", "cpu": [ "x64" ], @@ -1409,9 +1569,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.8.tgz", - "integrity": "sha512-CXBPVFkpDjM67sS1psWohZ6g/2/cd+cq56vPxK4JeawelxwK4YECgl9Y9TjkE2qfF+9/s1tHHJqrC4SS6cVvSg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", + "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -1429,7 +1589,7 @@ "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.10", + "@napi-rs/wasm-runtime": "^0.2.11", "@tybys/wasm-util": "^0.9.0", "tslib": "^2.8.0" }, @@ -1438,9 +1598,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.8.tgz", - "integrity": "sha512-7GmYk1n28teDHUjPlIx4Z6Z4hHEgvP5ZW2QS9ygnDAdI/myh3HTHjDqtSqgu1BpRoI4OiLx+fThAyA1JePoENA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", + "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", "cpu": [ "arm64" ], @@ -1454,9 +1614,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.8.tgz", - "integrity": "sha512-fou+U20j+Jl0EHwK92spoWISON2OBnCazIc038Xj2TdweYV33ZRkS9nwqiUi2d/Wba5xg5UoHfvynnb/UB49cQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", + "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", "cpu": [ "x64" ], @@ -1470,23 +1630,23 @@ } }, "node_modules/@tailwindcss/vite": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.8.tgz", - "integrity": "sha512-CQ+I8yxNV5/6uGaJjiuymgw0kEQiNKRinYbZXPdx1fk5WgiyReG0VaUx/Xq6aVNSUNJFzxm6o8FNKS5aMaim5A==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", + "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.1.8", - "@tailwindcss/oxide": "4.1.8", - "tailwindcss": "4.1.8" + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "tailwindcss": "4.1.11" }, "peerDependencies": { - "vite": "^5.2.0 || ^6" + "vite": "^5.2.0 || ^6 || ^7" } }, "node_modules/@tanstack/virtual-core": { - "version": "3.13.9", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.9.tgz", - "integrity": "sha512-3jztt0jpaoJO5TARe2WIHC1UQC3VMLAFUW5mmMo0yrkwtDB2AQP0+sh10BVUpWrnvHjSLvzFizydtEGLCJKFoQ==", + "version": "3.13.12", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz", + "integrity": "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==", "license": "MIT", "funding": { "type": "github", @@ -1494,12 +1654,12 @@ } }, "node_modules/@tanstack/vue-virtual": { - "version": "3.13.9", - "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.9.tgz", - "integrity": "sha512-HsvHaOo+o52cVcPhomKDZ3CMpTF/B2qg+BhPHIQJwzn4VIqDyt/rRVqtIomG6jE83IFsE2vlr6cmx7h3dHA0SA==", + "version": "3.13.12", + "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.12.tgz", + "integrity": "sha512-vhF7kEU9EXWXh+HdAwKJ2m3xaOnTTmgcdXcF2pim8g4GvI7eRrk2YRuV5nUlZnd/NbCIX4/Ja2OZu5EjJL06Ww==", "license": "MIT", "dependencies": { - "@tanstack/virtual-core": "3.13.9" + "@tanstack/virtual-core": "3.13.12" }, "funding": { "type": "github", @@ -1509,6 +1669,19 @@ "vue": "^2.7.0 || ^3.0.0" } }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, "node_modules/@types/dompurify": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", @@ -1519,11 +1692,18 @@ } }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "license": "MIT", + "peer": true + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1531,11 +1711,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { - "version": "22.15.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", - "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", - "devOptional": true, + "version": "22.17.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.0.tgz", + "integrity": "sha512-bbAKTCqX5aNVryi7qXVMi+OkB3w/OyblodicMbvE38blyAz7GxXf6XYhklokijuPwwVg9sDLKRxt0ZHXQwZVfQ==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -1547,6 +1736,16 @@ "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", "license": "MIT" }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -1559,18 +1758,38 @@ "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", "license": "MIT" }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.1.tgz", - "integrity": "sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz", + "integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/type-utils": "8.33.1", - "@typescript-eslint/utils": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/type-utils": "8.38.0", + "@typescript-eslint/utils": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -1584,7 +1803,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.33.1", + "@typescript-eslint/parser": "^8.38.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } @@ -1600,16 +1819,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.1.tgz", - "integrity": "sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz", + "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/typescript-estree": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "debug": "^4.3.4" }, "engines": { @@ -1625,14 +1844,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.1.tgz", - "integrity": "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz", + "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.33.1", - "@typescript-eslint/types": "^8.33.1", + "@typescript-eslint/tsconfig-utils": "^8.38.0", + "@typescript-eslint/types": "^8.38.0", "debug": "^4.3.4" }, "engines": { @@ -1647,14 +1866,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz", - "integrity": "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz", + "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1" + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1665,9 +1884,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz", - "integrity": "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz", + "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==", "dev": true, "license": "MIT", "engines": { @@ -1682,14 +1901,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.1.tgz", - "integrity": "sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz", + "integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.33.1", - "@typescript-eslint/utils": "8.33.1", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/utils": "8.38.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -1706,9 +1926,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.1.tgz", - "integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz", + "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==", "dev": true, "license": "MIT", "engines": { @@ -1720,16 +1940,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz", - "integrity": "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz", + "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.33.1", - "@typescript-eslint/tsconfig-utils": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/project-service": "8.38.0", + "@typescript-eslint/tsconfig-utils": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1749,16 +1969,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.1.tgz", - "integrity": "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz", + "integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/typescript-estree": "8.33.1" + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1773,14 +1993,14 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz", - "integrity": "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz", + "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.33.1", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.38.0", + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1791,9 +2011,9 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1817,82 +2037,82 @@ } }, "node_modules/@volar/language-core": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.14.tgz", - "integrity": "sha512-X6beusV0DvuVseaOEy7GoagS4rYHgDHnTrdOj5jeUb49fW5ceQyP9Ej5rBhqgz2wJggl+2fDbbojq1XKaxDi6w==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz", + "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==", "dev": true, "license": "MIT", "dependencies": { - "@volar/source-map": "2.4.14" + "@volar/source-map": "2.4.15" } }, "node_modules/@volar/source-map": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.14.tgz", - "integrity": "sha512-5TeKKMh7Sfxo8021cJfmBzcjfY1SsXsPMMjMvjY7ivesdnybqqS+GxGAoXHAOUawQTwtdUxgP65Im+dEmvWtYQ==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.15.tgz", + "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==", "dev": true, "license": "MIT" }, "node_modules/@volar/typescript": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.14.tgz", - "integrity": "sha512-p8Z6f/bZM3/HyCdRNFZOEEzts51uV8WHeN8Tnfnm2EBv6FDB2TQLzfVx7aJvnl8ofKAOnS64B2O8bImBFaauRw==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.15.tgz", + "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==", "dev": true, "license": "MIT", "dependencies": { - "@volar/language-core": "2.4.14", + "@volar/language-core": "2.4.15", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "node_modules/@vue/compiler-core": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.16.tgz", - "integrity": "sha512-AOQS2eaQOaaZQoL1u+2rCJIKDruNXVBZSiUD3chnUrsoX5ZTQMaCvXlWNIfxBJuU15r1o7+mpo5223KVtIhAgQ==", + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.18.tgz", + "integrity": "sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.2", - "@vue/shared": "3.5.16", + "@babel/parser": "^7.28.0", + "@vue/shared": "3.5.18", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.16.tgz", - "integrity": "sha512-SSJIhBr/teipXiXjmWOVWLnxjNGo65Oj/8wTEQz0nqwQeP75jWZ0n4sF24Zxoht1cuJoWopwj0J0exYwCJ0dCQ==", + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.18.tgz", + "integrity": "sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/compiler-core": "3.5.18", + "@vue/shared": "3.5.18" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.16.tgz", - "integrity": "sha512-rQR6VSFNpiinDy/DVUE0vHoIDUF++6p910cgcZoaAUm3POxgNOOdS/xgoll3rNdKYTYPnnbARDCZOyZ+QSe6Pw==", + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.18.tgz", + "integrity": "sha512-5aBjvGqsWs+MoxswZPoTB9nSDb3dhd1x30xrrltKujlCxo48j8HGDNj3QPhF4VIS0VQDUrA1xUfp2hEa+FNyXA==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.2", - "@vue/compiler-core": "3.5.16", - "@vue/compiler-dom": "3.5.16", - "@vue/compiler-ssr": "3.5.16", - "@vue/shared": "3.5.16", + "@babel/parser": "^7.28.0", + "@vue/compiler-core": "3.5.18", + "@vue/compiler-dom": "3.5.18", + "@vue/compiler-ssr": "3.5.18", + "@vue/shared": "3.5.18", "estree-walker": "^2.0.2", "magic-string": "^0.30.17", - "postcss": "^8.5.3", + "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.16.tgz", - "integrity": "sha512-d2V7kfxbdsjrDSGlJE7my1ZzCXViEcqN6w14DOsDrUCHEA6vbnVCpRFfrc4ryCP/lCKzX2eS1YtnLE/BuC9f/A==", + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.18.tgz", + "integrity": "sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/compiler-dom": "3.5.18", + "@vue/shared": "3.5.18" } }, "node_modules/@vue/compiler-vue2": { @@ -1906,17 +2126,50 @@ "he": "^1.2.0" } }, + "node_modules/@vue/devtools-api": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz", + "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.7" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz", + "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.7", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz", + "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, "node_modules/@vue/eslint-config-typescript": { - "version": "14.5.0", - "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.5.0.tgz", - "integrity": "sha512-5oPOyuwkw++AP5gHDh5YFmST50dPfWOcm3/W7Nbh42IK5O3H74ytWAw0TrCRTaBoD/02khnWXuZf1Bz1xflavQ==", + "version": "14.6.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.6.0.tgz", + "integrity": "sha512-UpiRY/7go4Yps4mYCjkvlIbVWmn9YvPGQDxTAlcKLphyaD77LjIu3plH4Y9zNT0GB4f3K5tMmhhtRhPOgrQ/bQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "^8.26.0", + "@typescript-eslint/utils": "^8.35.1", "fast-glob": "^3.3.3", - "typescript-eslint": "^8.26.0", - "vue-eslint-parser": "^10.1.1" + "typescript-eslint": "^8.35.1", + "vue-eslint-parser": "^10.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1933,13 +2186,13 @@ } }, "node_modules/@vue/language-core": { - "version": "2.2.10", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.10.tgz", - "integrity": "sha512-+yNoYx6XIKuAO8Mqh1vGytu8jkFEOH5C8iOv3i8Z/65A7x9iAOXA97Q+PqZ3nlm2lxf5rOJuIGI/wDtx/riNYw==", + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.12.tgz", + "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==", "dev": true, "license": "MIT", "dependencies": { - "@volar/language-core": "~2.4.11", + "@volar/language-core": "2.4.15", "@vue/compiler-dom": "^3.5.0", "@vue/compiler-vue2": "^2.7.16", "@vue/shared": "^3.5.0", @@ -1958,53 +2211,53 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.16.tgz", - "integrity": "sha512-FG5Q5ee/kxhIm1p2bykPpPwqiUBV3kFySsHEQha5BJvjXdZTUfmya7wP7zC39dFuZAcf/PD5S4Lni55vGLMhvA==", + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.18.tgz", + "integrity": "sha512-x0vPO5Imw+3sChLM5Y+B6G1zPjwdOri9e8V21NnTnlEvkxatHEH5B5KEAJcjuzQ7BsjGrKtfzuQ5eQwXh8HXBg==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.16" + "@vue/shared": "3.5.18" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.16.tgz", - "integrity": "sha512-bw5Ykq6+JFHYxrQa7Tjr+VSzw7Dj4ldR/udyBZbq73fCdJmyy5MPIFR9IX/M5Qs+TtTjuyUTCnmK3lWWwpAcFQ==", + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.18.tgz", + "integrity": "sha512-DUpHa1HpeOQEt6+3nheUfqVXRog2kivkXHUhoqJiKR33SO4x+a5uNOMkV487WPerQkL0vUuRvq/7JhRgLW3S+w==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/reactivity": "3.5.18", + "@vue/shared": "3.5.18" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.16.tgz", - "integrity": "sha512-T1qqYJsG2xMGhImRUV9y/RseB9d0eCYZQ4CWca9ztCuiPj/XWNNN+lkNBuzVbia5z4/cgxdL28NoQCvC0Xcfww==", + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.18.tgz", + "integrity": "sha512-YwDj71iV05j4RnzZnZtGaXwPoUWeRsqinblgVJwR8XTXYZ9D5PbahHQgsbmzUvCWNF6x7siQ89HgnX5eWkr3mw==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.16", - "@vue/runtime-core": "3.5.16", - "@vue/shared": "3.5.16", + "@vue/reactivity": "3.5.18", + "@vue/runtime-core": "3.5.18", + "@vue/shared": "3.5.18", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.16.tgz", - "integrity": "sha512-BrX0qLiv/WugguGsnQUJiYOE0Fe5mZTwi6b7X/ybGB0vfrPH9z0gD/Y6WOR1sGCgX4gc25L1RYS5eYQKDMoNIg==", + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.18.tgz", + "integrity": "sha512-PvIHLUoWgSbDG7zLHqSqaCoZvHi6NNmfVFOqO+OnwvqMz/tqQr3FuGWS8ufluNddk7ZLBJYMrjcw1c6XzR12mA==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/compiler-ssr": "3.5.18", + "@vue/shared": "3.5.18" }, "peerDependencies": { - "vue": "3.5.16" + "vue": "3.5.18" } }, "node_modules/@vue/shared": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.16.tgz", - "integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==", + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.18.tgz", + "integrity": "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==", "license": "MIT" }, "node_modules/@vueuse/core": { @@ -2043,10 +2296,24 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "optional": true, + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -2070,7 +2337,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -2140,13 +2407,13 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", - "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -2157,6 +2424,46 @@ "dev": true, "license": "MIT" }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/birpc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.5.0.tgz", + "integrity": "sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "optional": true, + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -2164,10 +2471,19 @@ "dev": true, "license": "ISC" }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2187,6 +2503,55 @@ "node": ">=8" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "license": "MIT", + "peer": true, + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -2289,6 +2654,19 @@ "node": ">=12" } }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "license": "MIT", + "peer": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -2336,9 +2714,9 @@ "license": "MIT" }, "node_modules/concurrently": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.2.tgz", - "integrity": "sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.0.tgz", + "integrity": "sha512-IsB/fiXTupmagMW4MNp2lx2cdSN2FfZq78vF90LBB+zZHArbIQZjQtzXCiXnvTxCZSvXanTqFLWBjw2UkLx1SQ==", "license": "MIT", "dependencies": { "chalk": "^4.1.2", @@ -2360,11 +2738,83 @@ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "optional": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -2415,7 +2865,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2429,17 +2878,94 @@ } } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", "license": "MIT" }, "node_modules/delayed-stream": { @@ -2451,6 +2977,16 @@ "node": ">=0.4.0" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/detect-libc": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", @@ -2460,6 +2996,14 @@ "node": ">=8" } }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/dompurify": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", @@ -2483,16 +3027,71 @@ "node": ">= 0.4" } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT", + "optional": true + }, + "node_modules/electron": { + "version": "37.2.5", + "resolved": "https://registry.npmjs.org/electron/-/electron-37.2.5.tgz", + "integrity": "sha512-719ZqEp43rj6xDJMICm4CIXl8keFFgvVNO9Ix6OtjNjrh9HtYlP/1WiYeRohnXj06aLyGx5NCzrHbG7j3BxO9w==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^22.7.7", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/electron-audio-loopback": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/electron-audio-loopback/-/electron-audio-loopback-1.0.5.tgz", + "integrity": "sha512-E/xneHrk2tLD7JntbjBJJr4HyWGaLrdGvqmoeBJZg9URbBBm2OgTEZ5TWgTkIPiAwEvAllsV+VfdBOfP4FeMkw==", + "license": "MIT", + "peerDependencies": { + "electron": ">=31.0.1" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "peer": true, + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", + "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -2514,6 +3113,16 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2560,19 +3169,27 @@ } }, "node_modules/es-toolkit": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.0.tgz", - "integrity": "sha512-EoVtfsblZAUBu1pLM7SS0JH6mOUSln+l7IPkdTAasDFO1jYJIrdFv6usRuGbUFKIuA78OVlvpGldGSztAihQQQ==", + "version": "1.39.8", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.8.tgz", + "integrity": "sha512-A8QO9TfF+rltS8BXpdu8OS+rpGgEdnRhqIVxO/ZmNvnXBYgOdSsxukT55ELyP94gZIntWJ+Li9QRrT2u1Kitpg==", "license": "MIT", "workspaces": [ "docs", "benchmarks" ] }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/esbuild": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", - "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -2582,31 +3199,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.5", - "@esbuild/android-arm": "0.25.5", - "@esbuild/android-arm64": "0.25.5", - "@esbuild/android-x64": "0.25.5", - "@esbuild/darwin-arm64": "0.25.5", - "@esbuild/darwin-x64": "0.25.5", - "@esbuild/freebsd-arm64": "0.25.5", - "@esbuild/freebsd-x64": "0.25.5", - "@esbuild/linux-arm": "0.25.5", - "@esbuild/linux-arm64": "0.25.5", - "@esbuild/linux-ia32": "0.25.5", - "@esbuild/linux-loong64": "0.25.5", - "@esbuild/linux-mips64el": "0.25.5", - "@esbuild/linux-ppc64": "0.25.5", - "@esbuild/linux-riscv64": "0.25.5", - "@esbuild/linux-s390x": "0.25.5", - "@esbuild/linux-x64": "0.25.5", - "@esbuild/netbsd-arm64": "0.25.5", - "@esbuild/netbsd-x64": "0.25.5", - "@esbuild/openbsd-arm64": "0.25.5", - "@esbuild/openbsd-x64": "0.25.5", - "@esbuild/sunos-x64": "0.25.5", - "@esbuild/win32-arm64": "0.25.5", - "@esbuild/win32-ia32": "0.25.5", - "@esbuild/win32-x64": "0.25.5" + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" } }, "node_modules/escalade": { @@ -2618,11 +3236,18 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT", + "optional": true + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -2632,20 +3257,20 @@ } }, "node_modules/eslint": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", - "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz", + "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", - "@eslint/config-helpers": "^0.2.1", - "@eslint/core": "^0.14.0", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.28.0", - "@eslint/plugin-kit": "^0.3.1", + "@eslint/js": "9.32.0", + "@eslint/plugin-kit": "^0.3.4", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -2656,9 +3281,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -2693,9 +3318,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "10.1.5", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz", - "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", "bin": { @@ -2782,6 +3407,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint-plugin-vue/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint-plugin-vue/node_modules/vue-eslint-parser": { "version": "9.4.3", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", @@ -2808,9 +3446,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -2838,9 +3476,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -2849,9 +3487,9 @@ } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2875,15 +3513,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2893,9 +3531,9 @@ } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2957,11 +3595,124 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "optional": true, + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.3.tgz", + "integrity": "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "optional": true, + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -2998,7 +3749,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/fast-levenshtein": { @@ -3018,6 +3769,16 @@ "reusify": "^1.0.4" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", + "peer": true, + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3031,6 +3792,13 @@ "node": ">=16.0.0" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT", + "optional": true + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -3044,6 +3812,24 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3083,9 +3869,9 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", @@ -3103,9 +3889,9 @@ } }, "node_modules/form-data": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", - "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -3118,6 +3904,62 @@ "node": ">= 6" } }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3187,6 +4029,22 @@ "node": ">= 0.4" } }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "peer": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -3200,6 +4058,25 @@ "node": ">=10.13.0" } }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -3207,10 +4084,28 @@ "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/gopd": { @@ -3225,6 +4120,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -3247,6 +4168,20 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -3296,6 +4231,73 @@ "he": "bin/he" } }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause", + "peer": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "license": "MIT", + "peer": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3333,6 +4335,23 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC", + "optional": true + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3375,17 +4394,36 @@ "node": ">=0.12.0" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT", + "optional": true + }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/jiti": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -3408,14 +4446,13 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { @@ -3425,11 +4462,28 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC", + "optional": true, + "peer": true + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "license": "MIT", + "peer": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -3725,6 +4779,16 @@ "dev": true, "license": "MIT" }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/lucide-vue-next": { "version": "0.468.0", "resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-0.468.0.tgz", @@ -3744,9 +4808,9 @@ } }, "node_modules/marked": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-16.0.0.tgz", - "integrity": "sha512-MUKMXDjsD/eptB7GPzxo4xcnLS6oo7/RHimUMHEDRhUooPwmN9BEpMl7AEOJv3bmso169wHI2wUF9VQgL7zfmA==", + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.1.1.tgz", + "integrity": "sha512-ij/2lXfCRT71L6u0M29tJPhP0bM5shLL3u5BePhFwPELj2blMJ6GDtD7PfJhRLhJ/c2UwrK17ySVcDzy2YHjHQ==", "license": "MIT", "bin": { "marked": "bin/marked.js" @@ -3755,6 +4819,20 @@ "node": ">= 20" } }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -3764,6 +4842,29 @@ "node": ">= 0.4" } }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3789,26 +4890,38 @@ } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", + "optional": true, "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "license": "MIT", + "optional": true, "dependencies": { - "mime-db": "1.52.0" + "mime-db": "^1.54.0" }, "engines": { "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -3846,6 +4959,12 @@ "node": ">= 18" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, "node_modules/mkdirp": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", @@ -3865,7 +4984,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/muggle-string": { @@ -3900,6 +5018,51 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true + }, + "node_modules/node-mac-permissions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/node-mac-permissions/-/node-mac-permissions-2.5.0.tgz", + "integrity": "sha512-zR8SVCaN3WqV1xwWd04XVAdzm3UTdjbxciLrZtB0Cc7F2Kd34AJfhPD4hm1HU0YH3oGUZO4X9OBLY5ijSTHsGw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^7.1.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -3913,6 +5076,16 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -3925,12 +5098,66 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ohash": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", "license": "MIT" }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "optional": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openai": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-5.11.0.tgz", + "integrity": "sha512-+AuTc5pVjlnTuA9zvn8rA/k+1RluPIx9AD4eDcnutv6JNwHHZxIhkFy+tmMKCvmMFDQzfA/r1ujvPWB19DQkYg==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3949,6 +5176,16 @@ "node": ">= 0.8.0" } }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3994,6 +5231,16 @@ "node": ">=6" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", @@ -4015,12 +5262,35 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT", + "peer": true + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4033,16 +5303,47 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "license": "MIT", "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.3.tgz", + "integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.2" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=16.20.0" } }, "node_modules/postcss": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", - "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "funding": [ { "type": "opencollective", @@ -4092,9 +5393,9 @@ } }, "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", "bin": { @@ -4108,15 +5409,15 @@ } }, "node_modules/prettier-plugin-organize-imports": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.1.0.tgz", - "integrity": "sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.2.0.tgz", + "integrity": "sha512-Zdy27UhlmyvATZi67BTnLcKTo8fm6Oik59Sz6H64PgZJVs6NJpPD1mT240mmJn62c98/QaL+r3kx9Q3gRpDajg==", "dev": true, "license": "MIT", "peerDependencies": { "prettier": ">=2.0", "typescript": ">=2.9", - "vue-tsc": "^2.1.0" + "vue-tsc": "^2.1.0 || 3" }, "peerDependenciesMeta": { "vue-tsc": { @@ -4125,9 +5426,9 @@ } }, "node_modules/prettier-plugin-tailwindcss": { - "version": "0.6.12", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.12.tgz", - "integrity": "sha512-OuTQKoqNwV7RnxTPwXWzOFXy6Jc4z8oeRZYGuMpRyG3WbuR3jjXdQFK8qFBMBx8UHWdHrddARz2fgUenild6aw==", + "version": "0.6.14", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz", + "integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==", "dev": true, "license": "MIT", "engines": { @@ -4135,6 +5436,8 @@ }, "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-hermes": "*", + "@prettier/plugin-oxc": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", @@ -4156,6 +5459,12 @@ "@ianvs/prettier-plugin-sort-imports": { "optional": true }, + "@prettier/plugin-hermes": { + "optional": true + }, + "@prettier/plugin-oxc": { + "optional": true + }, "@prettier/plugin-pug": { "optional": true }, @@ -4203,17 +5512,52 @@ } } }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "optional": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "peer": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -4255,10 +5599,49 @@ ], "license": "MIT" }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "optional": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/reka-ui": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.3.0.tgz", - "integrity": "sha512-HKvJej9Sc0KYEvTAbsGHgOxpEWL4FWSR70Q6Ld+bVNuaCxK6LP3jyTtyTWS+A44hHA9/aYfOBZ1Q8WkgZsGZpA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.4.1.tgz", + "integrity": "sha512-NB7DrCsODN8MH02BWtgiExygfFcuuZ5/PTn6fMgjppmFHqePvNhmSn1LEuF35nel6PFbA4v+gdj0IoGN1yZ+vw==", "license": "MIT", "dependencies": { "@floating-ui/dom": "^1.6.13", @@ -4285,6 +5668,13 @@ "node": ">=0.10.0" } }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "license": "MIT", + "peer": true + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -4295,6 +5685,19 @@ "node": ">=4" } }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "license": "MIT", + "peer": true, + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -4306,13 +5709,38 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/rollup": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", - "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", + "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", "license": "MIT", "dependencies": { - "@types/estree": "1.0.7" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -4322,33 +5750,33 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.41.1", - "@rollup/rollup-android-arm64": "4.41.1", - "@rollup/rollup-darwin-arm64": "4.41.1", - "@rollup/rollup-darwin-x64": "4.41.1", - "@rollup/rollup-freebsd-arm64": "4.41.1", - "@rollup/rollup-freebsd-x64": "4.41.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", - "@rollup/rollup-linux-arm-musleabihf": "4.41.1", - "@rollup/rollup-linux-arm64-gnu": "4.41.1", - "@rollup/rollup-linux-arm64-musl": "4.41.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", - "@rollup/rollup-linux-riscv64-gnu": "4.41.1", - "@rollup/rollup-linux-riscv64-musl": "4.41.1", - "@rollup/rollup-linux-s390x-gnu": "4.41.1", - "@rollup/rollup-linux-x64-gnu": "4.41.1", - "@rollup/rollup-linux-x64-musl": "4.41.1", - "@rollup/rollup-win32-arm64-msvc": "4.41.1", - "@rollup/rollup-win32-ia32-msvc": "4.41.1", - "@rollup/rollup-win32-x64-msvc": "4.41.1", + "@rollup/rollup-android-arm-eabi": "4.46.2", + "@rollup/rollup-android-arm64": "4.46.2", + "@rollup/rollup-darwin-arm64": "4.46.2", + "@rollup/rollup-darwin-x64": "4.46.2", + "@rollup/rollup-freebsd-arm64": "4.46.2", + "@rollup/rollup-freebsd-x64": "4.46.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", + "@rollup/rollup-linux-arm-musleabihf": "4.46.2", + "@rollup/rollup-linux-arm64-gnu": "4.46.2", + "@rollup/rollup-linux-arm64-musl": "4.46.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", + "@rollup/rollup-linux-ppc64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-musl": "4.46.2", + "@rollup/rollup-linux-s390x-gnu": "4.46.2", + "@rollup/rollup-linux-x64-gnu": "4.46.2", + "@rollup/rollup-linux-x64-musl": "4.46.2", + "@rollup/rollup-win32-arm64-msvc": "4.46.2", + "@rollup/rollup-win32-ia32-msvc": "4.46.2", + "@rollup/rollup-win32-x64-msvc": "4.46.2", "fsevents": "~2.3.2" } }, "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz", - "integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", + "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", "cpu": [ "x64" ], @@ -4358,6 +5786,23 @@ "linux" ] }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4391,11 +5836,39 @@ "tslib": "^2.1.0" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT", + "optional": true + }, "node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4404,11 +5877,82 @@ "node": ">=10" } }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC", + "optional": true + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -4421,7 +5965,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -4520,6 +6064,33 @@ "node": ">=0.10.0" } }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause", + "optional": true, + "peer": true + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -4559,6 +6130,31 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/superjson": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", + "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", + "license": "MIT", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -4575,9 +6171,9 @@ } }, "node_modules/tailwind-merge": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.0.tgz", - "integrity": "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", + "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==", "license": "MIT", "funding": { "type": "github", @@ -4585,9 +6181,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.8.tgz", - "integrity": "sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", "license": "MIT" }, "node_modules/tapable": { @@ -4633,9 +6229,9 @@ } }, "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", - "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" @@ -4647,9 +6243,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", "engines": { "node": ">=12" @@ -4671,6 +6267,16 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -4700,9 +6306,9 @@ "license": "0BSD" }, "node_modules/tw-animate-css": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.4.tgz", - "integrity": "sha512-dd1Ht6/YQHcNbq0znIT6dG8uhO7Ce+VIIhZUhjsryXsMPJQz3bZg7Q2eNzLwipb25bRZslGb2myio5mScd1TFg==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.6.tgz", + "integrity": "sha512-9dy0R9UsYEGmgf26L8UcHiLmSFTHa9+D7+dAt/G/sF5dCnPePZbfgDYinc7/UzAM7g/baVrmS6m9yEpU46d+LA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/Wombosvideo" @@ -4722,11 +6328,12 @@ } }, "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", "license": "(MIT OR CC0-1.0)", + "optional": true, + "peer": true, "engines": { "node": ">=10" }, @@ -4734,6 +6341,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "optional": true, + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -4748,15 +6370,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.33.1.tgz", - "integrity": "sha512-AgRnV4sKkWOiZ0Kjbnf5ytTJXMUZQ0qhSVdQtDNYLPLnjsATEYhaO94GlRQwi4t4gO8FfjM6NnikHeKjUm8D7A==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.38.0.tgz", + "integrity": "sha512-FsZlrYK6bPDGoLeZRuvx2v6qrM03I0U0SnfCLPs/XCCPCFD80xU9Pg09H/K+XFa68uJuZo7l/Xhs+eDRg2l3hg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.33.1", - "@typescript-eslint/parser": "8.33.1", - "@typescript-eslint/utils": "8.33.1" + "@typescript-eslint/eslint-plugin": "8.38.0", + "@typescript-eslint/parser": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/utils": "8.38.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4774,14 +6397,33 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "devOptional": true, "license": "MIT" }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -4794,6 +6436,16 @@ "dev": true, "license": "MIT" }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vite": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", @@ -4879,9 +6531,9 @@ } }, "node_modules/vite/node_modules/fdir": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", - "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" @@ -4893,9 +6545,9 @@ } }, "node_modules/vite/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", "engines": { "node": ">=12" @@ -4912,16 +6564,16 @@ "license": "MIT" }, "node_modules/vue": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.16.tgz", - "integrity": "sha512-rjOV2ecxMd5SiAmof2xzh2WxntRcigkX/He4YFJ6WdRvVUrbt6DxC1Iujh10XLl8xCDRDtGKMeO3D+pRQ1PP9w==", + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.18.tgz", + "integrity": "sha512-7W4Y4ZbMiQ3SEo+m9lnoNpV9xG7QVMLa+/0RFwwiAVkeYoyGXqWE85jabU4pllJNUzqfLShJ5YLptewhCWUgNA==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.16", - "@vue/compiler-sfc": "3.5.16", - "@vue/runtime-dom": "3.5.16", - "@vue/server-renderer": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/compiler-dom": "3.5.18", + "@vue/compiler-sfc": "3.5.18", + "@vue/runtime-dom": "3.5.18", + "@vue/server-renderer": "3.5.18", + "@vue/shared": "3.5.18" }, "peerDependencies": { "typescript": "*" @@ -4933,9 +6585,9 @@ } }, "node_modules/vue-eslint-parser": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.1.3.tgz", - "integrity": "sha512-dbCBnd2e02dYWsXoqX5yKUZlOt+ExIpq7hmHKPb5ZqKcjf++Eo0hMseFTZMLKThrUk61m+Uv6A2YSBve6ZvuDQ==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.2.0.tgz", + "integrity": "sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==", "dev": true, "license": "MIT", "dependencies": { @@ -4944,7 +6596,6 @@ "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.6.0", - "lodash": "^4.17.21", "semver": "^7.6.3" }, "engines": { @@ -4958,9 +6609,9 @@ } }, "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4971,14 +6622,14 @@ } }, "node_modules/vue-tsc": { - "version": "2.2.10", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.10.tgz", - "integrity": "sha512-jWZ1xSaNbabEV3whpIDMbjVSVawjAyW+x1n3JeGQo7S0uv2n9F/JMgWW90tGWNFRKya4YwKMZgCtr0vRAM7DeQ==", + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.12.tgz", + "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==", "dev": true, "license": "MIT", "dependencies": { - "@volar/typescript": "~2.4.11", - "@vue/language-core": "2.2.10" + "@volar/typescript": "2.4.15", + "@vue/language-core": "2.2.12" }, "bin": { "vue-tsc": "bin/vue-tsc.js" @@ -4991,7 +6642,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -5030,6 +6681,33 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", @@ -5085,6 +6763,17 @@ "node": ">=12" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -5119,6 +6808,26 @@ "funding": { "url": "https://github.com/sponsors/ljharb" } + }, + "node_modules/zod": { + "version": "3.25.67", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", + "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "license": "ISC", + "optional": true, + "peerDependencies": { + "zod": "^3.24.1" + } } } } diff --git a/package.json b/package.json index 8e8583e..f36fb27 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ }, "dependencies": { "@inertiajs/vue3": "^2.0.0", + "@openai/agents": "^0.0.12", + "@openai/agents-realtime": "^0.0.12", "@tailwindcss/vite": "^4.1.1", "@types/dompurify": "^3.0.5", "@vitejs/plugin-vue": "^5.2.1", @@ -36,9 +38,11 @@ "concurrently": "^9.0.1", "date-fns": "^4.1.0", "dompurify": "^3.2.6", + "electron-audio-loopback": "^1.0.5", "laravel-vite-plugin": "^1.0", "lucide-vue-next": "^0.468.0", "marked": "^16.0.0", + "pinia": "^3.0.3", "reka-ui": "^2.2.0", "tailwind-merge": "^3.2.0", "tailwindcss": "^4.1.1", @@ -51,6 +55,7 @@ "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "4.9.5", "@tailwindcss/oxide-linux-x64-gnu": "^4.0.1", - "lightningcss-linux-x64-gnu": "^1.29.1" + "lightningcss-linux-x64-gnu": "^1.29.1", + "node-mac-permissions": "^2.5.0" } } diff --git a/resources/js/app.ts b/resources/js/app.ts index da710e3..a34cbf5 100644 --- a/resources/js/app.ts +++ b/resources/js/app.ts @@ -3,6 +3,7 @@ import '../css/app.css'; import { createInertiaApp } from '@inertiajs/vue3'; import axios from 'axios'; import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; +import { createPinia } from 'pinia'; import type { DefineComponent } from 'vue'; import { createApp, h } from 'vue'; import { ZiggyVue } from 'ziggy-js'; @@ -17,6 +18,8 @@ if (token) { const appName = import.meta.env.VITE_APP_NAME || 'Laravel'; +const pinia = createPinia(); + createInertiaApp({ title: (title) => (title ? `${title} - ${appName}` : appName), resolve: (name) => resolvePageComponent(`./pages/${name}.vue`, import.meta.glob('./pages/**/*.vue')), @@ -24,6 +27,7 @@ createInertiaApp({ createApp({ render: () => h(App, props) }) .use(plugin) .use(ZiggyVue) + .use(pinia) .mount(el); }, progress: { diff --git a/resources/js/components/AppSidebar.vue b/resources/js/components/AppSidebar.vue index 3c47f1c..c8c8143 100644 --- a/resources/js/components/AppSidebar.vue +++ b/resources/js/components/AppSidebar.vue @@ -10,7 +10,7 @@ import AppLogo from './AppLogo.vue'; const mainNavItems: NavItem[] = [ { title: 'Realtime Agent', - href: '/realtime-agent', + href: '/realtime-agent-v2', icon: Phone, }, { @@ -55,7 +55,7 @@ const footerNavItems: NavItem[] = [ - + diff --git a/resources/js/components/ContextualInformation.vue b/resources/js/components/ContextualInformation.vue index 2cc6e3b..531c553 100644 --- a/resources/js/components/ContextualInformation.vue +++ b/resources/js/components/ContextualInformation.vue @@ -1,6 +1,6 @@ @@ -692,6 +649,7 @@ import { useVariables } from '@/composables/useVariables'; import { audioHealthMonitor, type AudioHealthStatus } from '@/services/audioHealthCheck'; import { router } from '@inertiajs/vue3'; import axios from 'axios'; +import { useSettingsStore } from '@/stores/settings'; // import type { TemplateResolution } from '@/types/variable' import ContextualInformation from '@/components/ContextualInformation.vue'; @@ -880,12 +838,6 @@ const transcriptQueue: Array<{ speaker: string; text: string; timestamp: number; const insightQueue: Array<{ type: string; data: any; timestamp: number }> = []; let saveInterval: NodeJS.Timeout | null = null; -// Customer info modal -const showCustomerModal = ref(false); -const customerInfo = ref({ - name: '', - company: '', -}); // Coach dropdown const showCoachDropdown = ref(false); @@ -1007,11 +959,6 @@ const scrollTranscriptToTop = () => { nextTick(() => { if (transcriptContainer.value) { // For flex-col-reverse, scrolling to max height shows the newest messages - console.log('🔄 Auto-scrolling transcript:', { - scrollHeight: transcriptContainer.value.scrollHeight, - clientHeight: transcriptContainer.value.clientHeight, - currentScrollTop: transcriptContainer.value.scrollTop, - }); transcriptContainer.value.scrollTop = transcriptContainer.value.scrollHeight; } }); @@ -1083,25 +1030,13 @@ const toggleSession = async () => { if (isActive.value) { await stopSession(); } else { - // Show customer modal for new sessions - showCustomerModal.value = true; + await startSession(); } }; -const startWithCustomerInfo = async () => { - showCustomerModal.value = false; - await startSession(); -}; - -const skipCustomerInfo = async () => { - customerInfo.value = { name: '', company: '' }; - showCustomerModal.value = false; - await startSession(); -}; const startSession = async () => { try { - console.log('🚀 Starting Dual-Agent Realtime session...'); connectionStatus.value = 'connecting'; callStartTime.value = new Date(); callDurationSeconds.value = 0; @@ -1141,16 +1076,13 @@ const startSession = async () => { const wsUrl = `wss://api.openai.com/v1/realtime?model=gpt-4o-mini-realtime-preview-2024-12-17`; // 1. Salesperson Transcriber - Simple transcription only - console.log('🎤 Connecting Salesperson Transcriber...'); wsSalesperson = new WebSocket(wsUrl, ['realtime', `openai-insecure-api-key.${ephemeralKey}`, 'openai-beta.realtime-v1']); // 2. Customer Coach Agent - System audio + Real-time coaching - console.log('🧠 Connecting Customer Coach Agent...'); wsCustomerCoach = new WebSocket(wsUrl, ['realtime', `openai-insecure-api-key.${ephemeralKey}`, 'openai-beta.realtime-v1']); // Set up Salesperson Agent (Microphone only) wsSalesperson.onopen = () => { - console.log('✅ Salesperson Agent connected'); // Add small delay to ensure WebSocket is ready setTimeout(() => { @@ -1183,7 +1115,6 @@ const startSession = async () => { // Set up Customer Coach Agent (System audio + AI coaching) wsCustomerCoach.onopen = () => { - console.log('✅ Customer Coach Agent connected'); connectionStatus.value = 'connected'; // Get coach instructions from template @@ -1370,7 +1301,6 @@ Be thorough but not intrusive. Extract actionable intelligence to help guide the }; wsSalesperson.onclose = () => { - console.log('🔌 Salesperson agent disconnected'); if (isActive.value) { addToTranscript('system', '⚠️ Salesperson connection lost. Please restart the session.', 'error'); } @@ -1382,7 +1312,6 @@ Be thorough but not intrusive. Extract actionable intelligence to help guide the }; wsCustomerCoach.onclose = () => { - console.log('🔌 Customer Coach agent disconnected'); if (isActive.value) { addToTranscript('system', '⚠️ Customer Coach connection lost. Please restart the session.', 'error'); connectionStatus.value = 'disconnected'; @@ -1392,16 +1321,13 @@ Be thorough but not intrusive. Extract actionable intelligence to help guide the // Salesperson transcriber message handler wsSalesperson.onmessage = (event) => { const data = JSON.parse(event.data); - console.log('🎤 Salesperson transcriber:', data.type); switch (data.type) { case 'session.created': - console.log('✅ Salesperson transcriber ready'); break; case 'conversation.item.input_audio_transcription.completed': if (data.transcript) { - console.log('👔 Salesperson said:', data.transcript); // Track speaker transitions const now = Date.now(); @@ -1447,32 +1373,24 @@ Be thorough but not intrusive. Extract actionable intelligence to help guide the // Enhanced logging for debugging if (data.type.includes('function')) { - console.log('🎯 Function call:', data.type, data); } else if (data.type.includes('transcription')) { - console.log('📣 Customer transcription:', data.type); } else if (data.type === 'response.function_call_arguments.delta') { - console.log('📝 Streaming delta:', data.name, 'chars:', data.arguments?.length); } else { - console.log('🤖 Customer Coach:', data.type); } switch (data.type) { case 'session.created': - console.log('✅ Customer Coach agent ready'); isActive.value = true; break; case 'response.created': - console.log('🎬 Response created', data.response); activeResponseId = data.response?.id || null; - console.log('📝 Stored response ID:', activeResponseId); // Reset analyzing flag when response is created isAnalyzing = false; break; case 'conversation.item.input_audio_transcription.completed': if (data.transcript) { - console.log('📞 Customer said:', data.transcript); // Update UI immediately addToTranscript('customer', data.transcript); @@ -1512,7 +1430,6 @@ Be thorough but not intrusive. Extract actionable intelligence to help guide the if (shouldAnalyzeImmediately) { // Analyze immediately for important content - console.log('⚡ Immediate analysis triggered'); analyzeCustomerSpeech(); } else { // Otherwise debounce @@ -1524,7 +1441,6 @@ Be thorough but not intrusive. Extract actionable intelligence to help guide the break; case 'response.function_call_arguments.done': - console.log('✅ Function call complete:', data); // Get accumulated arguments if available const accumulatedArgs = functionCallAccumulator.get(data.call_id || '') || data.arguments; @@ -1550,7 +1466,6 @@ Be thorough but not intrusive. Extract actionable intelligence to help guide the if (data.call_id && data.arguments) { const existing = functionCallAccumulator.get(data.call_id) || ''; functionCallAccumulator.set(data.call_id, existing + data.arguments); - console.log('📡 Accumulating:', data.name, 'total length:', existing.length + data.arguments.length); } break; @@ -1561,18 +1476,15 @@ Be thorough but not intrusive. Extract actionable intelligence to help guide the // Check if it's the "already has active response" error if (data.error?.message?.includes('already has active response')) { - console.log('⚠️ Ignoring duplicate response error'); } break; case 'response.done': - console.log('✅ Coach response complete'); activeResponseId = null; isAnalyzing = false; break; case 'response.cancelled': - console.log('🚫 Response cancelled'); activeResponseId = null; isAnalyzing = false; break; @@ -1585,23 +1497,18 @@ Be thorough but not intrusive. Extract actionable intelligence to help guide the const data = JSON.parse(event.data) if (data.type.includes('function')) { - console.log('🏆 Coach function:', data.type, data) } else { - console.log('🏆 Coach agent:', data.type) } switch (data.type) { case 'session.created': - console.log('✅ Coach agent ready') isActive.value = true break case 'session.updated': - console.log('🔄 Coach session updated') break case 'response.function_call_arguments.done': - console.log('✅ Coach function call complete:', data) if (data.name && data.arguments) { try { const args = JSON.parse(data.arguments) @@ -1689,7 +1596,6 @@ Be thorough but not intrusive. Extract actionable intelligence to help guide the break case 'response.done': - console.log('✅ Coach response complete') // Reset flag when response is done coachResponsePending = false break @@ -1709,11 +1615,9 @@ Be thorough but not intrusive. Extract actionable intelligence to help guide the // Close handlers wsSalesperson.onclose = () => { - console.log('🔌 Salesperson agent disconnected'); }; wsCustomerCoach.onclose = () => { - console.log('🔌 Customer Coach agent disconnected'); connectionStatus.value = 'disconnected'; isActive.value = false; }; @@ -1742,7 +1646,6 @@ const analyzeCustomerSpeech = async () => { // Cancel any active response first - but only if we have a valid ID if (activeResponseId && activeResponseId !== 'pending') { - console.log('🚫 Cancelling previous response:', activeResponseId); try { wsCustomerCoach.send( JSON.stringify({ @@ -1760,7 +1663,6 @@ const analyzeCustomerSpeech = async () => { const combinedSpeech = customerSpeechBuffer.join(' '); customerSpeechBuffer = []; // Clear buffer - console.log('🧠 Analyzing customer speech:', combinedSpeech); // Create focused instruction for better results const instruction = `Analyze this customer statement: "${combinedSpeech}" @@ -1776,7 +1678,6 @@ IMPORTANT: Use the provided function tools to capture insights: Be thorough - capture ALL topics discussed, not just the main one. Each distinct topic should be tracked separately.`; // Create new response - ID will be assigned by the server - console.log('📤 Creating new response for analysis'); wsCustomerCoach.send( JSON.stringify({ @@ -1790,11 +1691,9 @@ Be thorough - capture ALL topics discussed, not just the main one. Each distinct }; const handleFunctionCall = (name: string, args: any) => { - console.log('🔧 Handling function call:', name, args); switch (name) { case 'track_discussion_topic': - console.log('📝 Tracking topic:', args.name, 'Sentiment:', args.sentiment); // Normalize topic name for comparison const normalizedName = args.name.trim().toLowerCase(); @@ -1809,7 +1708,6 @@ const handleFunctionCall = (name: string, args: any) => { if (args.sentiment && args.sentiment !== existingTopic.sentiment) { existingTopic.sentiment = 'mixed'; } - console.log('📈 Updated topic:', existingTopic.name, 'mentions:', existingTopic.mentions); } else { // Add new topic const newTopic = { @@ -1821,7 +1719,6 @@ const handleFunctionCall = (name: string, args: any) => { sentiment: args.sentiment || 'neutral', }; topics.value.push(newTopic); - console.log('🆕 New topic added:', newTopic.name); // Queue insight for saving insightQueue.push({ @@ -1833,8 +1730,6 @@ const handleFunctionCall = (name: string, args: any) => { // Sort topics by mentions topics.value.sort((a, b) => b.mentions - a.mentions); - console.log('📊 Total topics:', topics.value.length); - console.log('📈 All topics:', topics.value.map((t) => `${t.name} (${t.mentions}x)`).join(', ')); // Also add to transcript for visibility if (!existingTopic) { @@ -1843,7 +1738,6 @@ const handleFunctionCall = (name: string, args: any) => { break; case 'detect_commitment': - console.log('🤝 Commitment detected:', args.speaker, args.text); const newCommitment = { id: `commit-${Date.now()}`, @@ -1870,7 +1764,6 @@ const handleFunctionCall = (name: string, args: any) => { break; case 'analyze_customer_intent': - console.log('🧠 Customer analysis:', args); // Trigger visual feedback intelligenceUpdating.value = true; @@ -1883,7 +1776,6 @@ const handleFunctionCall = (name: string, args: any) => { buyingStage: args.buyingStage || customerIntelligence.value.buyingStage, }; - console.log('📈 Customer intel updated:', customerIntelligence.value); // Add system message for significant changes if (args.intent !== 'unknown' && args.intent !== customerIntelligence.value.intent) { @@ -1897,7 +1789,6 @@ const handleFunctionCall = (name: string, args: any) => { break; case 'highlight_insight': - console.log('💡 Insight captured:', args.type, args.text); const newInsight = { id: `insight-${Date.now()}`, @@ -1923,15 +1814,12 @@ const handleFunctionCall = (name: string, args: any) => { } // Log current insights to verify update - console.log('📊 Total insights:', insights.value.length); - console.log('🔝 Latest insight:', newInsight); // Add to transcript for visibility addToTranscript('system', `💡 ${args.type.replace('_', ' ')}: ${args.text}`, 'info'); break; case 'create_action_item': - console.log('📝 Action item created:', args.text); const newActionItem = { id: `action-${Date.now()}`, @@ -1961,12 +1849,10 @@ const handleFunctionCall = (name: string, args: any) => { transcript.value[lastSpeakerIndex].role = args.speaker; // Log identification for debugging - console.log(`🎯 Speaker identified: ${args.speaker} (${Math.round(args.confidence * 100)}% confidence)`); } break; case 'detect_information_need': - console.log('📋 Information need detected:', args.topic, 'Context:', args.context); // Update the conversation context for the contextual information card if (args.context) { @@ -1983,17 +1869,14 @@ const handleFunctionCall = (name: string, args: any) => { const setupAudioCapture = async () => { try { - console.log('🎤 Setting up dual audio capture...'); // First check if we can enumerate devices try { const devices = await navigator.mediaDevices.enumerateDevices(); const audioInputs = devices.filter((d) => d.kind === 'audioinput'); - console.log('🎙️ Available microphones:', audioInputs.length); // If no devices or all have empty labels, we need permission if (audioInputs.length === 0 || audioInputs.every((d) => !d.label)) { - console.log('⚠️ Microphone permission needed'); } } catch (enumError) { console.error('Error enumerating devices:', enumError); @@ -2010,28 +1893,22 @@ const setupAudioCapture = async () => { }, }); microphoneStatus.value = 'active'; - console.log('✅ Microphone access granted (Salesperson audio)'); // Try to setup system audio capture using Swift helper try { - console.log('🔍 Checking window.remote:', window.remote ? 'Available' : 'Not available'); - console.log('🔍 Window object keys:', Object.keys(window)); // Dynamic import to avoid issues in browser const { SystemAudioCapture, isSystemAudioAvailable } = await import('@/services/audioCapture'); // Check if system audio capture is available const isAvailable = await isSystemAudioAvailable(); - console.log('🔍 System audio available check result:', isAvailable); if (isAvailable) { - console.log('🔊 System audio capture available, starting...'); systemAudioCapture.value = new SystemAudioCapture(); // Check permission first - const hasPermission = await systemAudioCapture.value.checkPermission(); - console.log('🔒 Screen recording permission:', hasPermission ? 'granted' : 'denied'); + await systemAudioCapture.value.checkPermission(); // Handle audio data from system systemAudioCapture.value.on('audio', (pcm16: Int16Array) => { @@ -2042,11 +1919,7 @@ const setupAudioCapture = async () => { // Log every 20th packet for debugging if (Math.random() < 0.05) { - console.log('📞 System audio:', { - samples: pcm16.length, - level: systemAudioLevel.value, - source: 'customer', - }); + // Removed debug logging } // CRITICAL: Send as customer audio - this is from phone/zoom @@ -2056,7 +1929,6 @@ const setupAudioCapture = async () => { // Handle status updates systemAudioCapture.value.on('status', (state: string) => { - console.log('🔊 System audio status:', state); // Map status states to our UI states switch (state) { @@ -2112,7 +1984,6 @@ const setupAudioCapture = async () => { // Attach to health monitor audioHealthMonitor.attach(systemAudioCapture.value); - addToTranscript('system', '✅ Dual audio capture active: Microphone (You) + System Audio (Customer)', 'success'); } else { throw new Error('System audio capture not available'); } @@ -2150,11 +2021,7 @@ const setupAudioCapture = async () => { // Log occasionally for debugging if (Math.random() < 0.02) { - console.log('🎤 Microphone audio:', { - level: audioLevel.value, - source: audioSource, - mode: systemAudioStatus.value === 'active' ? 'dual' : 'mixed', - }); + // Removed debug logging } sendAudioWithMetadata(pcm16, audioSource); @@ -2166,8 +2033,6 @@ const setupAudioCapture = async () => { // Note: System audio is handled by the Swift helper, not through Web Audio API - console.log('✅ Audio pipeline setup complete'); - console.log(`Mode: ${systemAudioStatus.value === 'active' ? 'Dual Audio (Mic + Tab)' : 'Mixed Audio (Mic only)'}`); } catch (error) { console.error('❌ Failed to setup audio:', error); microphoneStatus.value = 'error'; @@ -2210,7 +2075,6 @@ const sendAudioWithMetadata = (pcm16: Int16Array, source: 'salesperson' | 'custo if (Math.random() < 0.02) { // Log 2% of the time - console.log('🎤 Sent audio to salesperson agent'); } } else if (source === 'customer' && wsCustomerCoach && wsCustomerCoach.readyState === WebSocket.OPEN) { // Send system audio to customer coach agent (direct to AI) @@ -2223,7 +2087,6 @@ const sendAudioWithMetadata = (pcm16: Int16Array, source: 'salesperson' | 'custo if (Math.random() < 0.02) { // Log 2% of the time - console.log('📞 Sent audio to customer coach agent for instant AI processing'); } } else if (source === 'mixed') { // Mixed audio mode - send to both agents (not ideal but fallback) @@ -2274,7 +2137,6 @@ const sendAudioWithMetadata = (pcm16: Int16Array, source: 'salesperson' | 'custo // audioRestartAttempts.value++ // // try { -// console.log('🔄 Restarting system audio capture...') // addToTranscript('system', '🔄 Restarting system audio capture...', 'warning') // // // Restart the audio capture @@ -2301,7 +2163,6 @@ const sendAudioWithMetadata = (pcm16: Int16Array, source: 'salesperson' | 'custo // } const stopSession = async () => { - console.log('🛑 Stopping session...'); isEndingCall.value = true; // Mark that we're intentionally ending // Add call ended message @@ -2355,7 +2216,6 @@ const stopSession = async () => { // Stop system audio capture if (systemAudioCapture.value) { - console.log('🔊 Stopping system audio capture...'); await systemAudioCapture.value.stop(); systemAudioCapture.value = null; } @@ -2376,8 +2236,6 @@ const stopSession = async () => { isActive.value = false; connectionStatus.value = 'disconnected'; - // Reset customer info for next session - customerInfo.value = { name: '', company: '' }; // Reset the ending flag after a delay setTimeout(() => { @@ -2412,12 +2270,11 @@ const startConversationSession = async () => { try { const response = await axios.post('/conversations', { template_used: selectedTemplate.value?.name || null, - customer_name: customerInfo.value.name || null, - customer_company: customerInfo.value.company || null, + customer_name: null, + customer_company: null, }); currentSessionId.value = response.data.session_id; - console.log('💾 Started conversation session:', currentSessionId.value); // Start periodic saving saveInterval = setInterval(() => { @@ -2442,7 +2299,6 @@ const endConversationSession = async () => { ai_summary: null, // Could generate summary here }); - console.log('💾 Ended conversation session:', currentSessionId.value); currentSessionId.value = null; } catch (error) { console.error('❌ Failed to end conversation session:', error); @@ -2473,7 +2329,6 @@ const saveQueuedData = async (force: boolean = false) => { })), }); - console.log('📝 Saved', transcriptsToSave.length, 'transcripts'); } // Save insights @@ -2489,7 +2344,6 @@ const saveQueuedData = async (force: boolean = false) => { })), }); - console.log('💡 Saved', insightsToSave.length, 'insights'); } } catch (error) { console.error('❌ Failed to save conversation data:', error); @@ -2503,24 +2357,15 @@ const saveQueuedData = async (force: boolean = false) => { // Template Management Methods const fetchTemplates = async () => { try { - console.log('🔄 Fetching templates...'); const response = await axios.get('/templates'); - console.log('📋 Templates response:', response.data); templates.value = response.data.templates || []; - console.log(`✅ Loaded ${templates.value.length} templates`); - console.log( - 'Available templates:', - templates.value.map((t) => t.name), - ); - // Load persisted template or select default const persistedTemplateId = localStorage.getItem('selectedTemplateId'); if (persistedTemplateId) { const persistedTemplate = templates.value.find((t) => t.id === persistedTemplateId); if (persistedTemplate) { selectedTemplate.value = persistedTemplate; - console.log('📌 Restored persisted template:', persistedTemplate.name); } } @@ -2531,7 +2376,6 @@ const fetchTemplates = async () => { const defaultTemplate = templates.value.find((t) => t.name === 'Sales Discovery Call') || templates.value[0]; if (defaultTemplate) { selectedTemplate.value = defaultTemplate; - console.log('📌 Selected default template:', defaultTemplate.name); } } else { // Handle case when no templates exist @@ -2546,7 +2390,6 @@ const fetchTemplates = async () => { }; const selectTemplateFromDropdown = async (template: Template) => { - console.log('🎯 Selecting template:', template.name); selectedTemplate.value = template; coveredPoints.value = []; // Reset covered points when changing template @@ -2608,8 +2451,6 @@ const updateCoachInstructions = async () => { }; wsCustomerCoach.send(JSON.stringify(updateMessage)); - console.log('🔄 Updated coach instructions with template:', selectedTemplate.value.name); - console.log('📊 Variables used:', mergedVariables); // Add notification to transcript transcript.value.push({ @@ -2662,6 +2503,15 @@ const handleBeforeUnload = (e: BeforeUnloadEvent) => { // Demo data for visualization onMounted(async () => { + // Initialize settings store with composable support status + const settingsStore = useSettingsStore(); + + // Wait a bit for composables to check support + setTimeout(() => { + settingsStore.setOverlaySupported(isOverlaySupported.value); + settingsStore.setProtectionSupported(isProtectionSupported.value); + }, 200); + // Check API key status first try { const response = await axios.get('/api/openai/status'); @@ -2682,7 +2532,6 @@ onMounted(async () => { await Promise.all([fetchTemplates(), loadVariables()]); // Check environment on mount - console.log('🔑 API Key is configured'); // Pre-check microphone permission to trigger prompt early if needed try { @@ -2691,12 +2540,9 @@ onMounted(async () => { // If we can't see device labels, we don't have permission yet if (audioInputs.length > 0 && audioInputs.every((d) => !d.label)) { - console.log('🎤 Microphone permission not granted yet'); // Don't request permission here - wait for user to start call } else if (audioInputs.length > 0) { - console.log('✅ Microphone already accessible'); } else { - console.log('⚠️ No microphone devices found'); } } catch (error) { console.error('Error checking microphone status:', error); diff --git a/resources/js/pages/RealtimeAgent/MainV2.vue b/resources/js/pages/RealtimeAgent/MainV2.vue new file mode 100644 index 0000000..fa17736 --- /dev/null +++ b/resources/js/pages/RealtimeAgent/MainV2.vue @@ -0,0 +1,1612 @@ + + + + + \ No newline at end of file diff --git a/resources/js/services/audioCapture.ts b/resources/js/services/audioCapture.ts index 7123aa2..dd2690d 100644 --- a/resources/js/services/audioCapture.ts +++ b/resources/js/services/audioCapture.ts @@ -125,7 +125,6 @@ export class SystemAudioCapture extends SimpleEventEmitter { // Kill any existing macos-audio-capture processes try { execSync('pkill -f macos-audio-capture', { encoding: 'utf8' }); - console.log('🔄 Killed existing audio capture processes'); // Wait a bit for processes to die await new Promise((resolve) => setTimeout(resolve, 500)); } catch { @@ -168,8 +167,6 @@ export class SystemAudioCapture extends SimpleEventEmitter { execPath = path.join(resourcesDir, 'app.asar.unpacked', 'resources', 'app', 'native', 'macos-audio-capture', 'macos-audio-capture'); } - console.log('App path:', appPath); - console.log('Starting audio capture from:', execPath); // Check if executable exists const fs = window.remote.require('fs'); @@ -189,7 +186,6 @@ export class SystemAudioCapture extends SimpleEventEmitter { throw error; } - console.log('✅ Swift executable found'); // Spawn the Swift process this.process = spawn(execPath, [], { @@ -209,7 +205,6 @@ export class SystemAudioCapture extends SimpleEventEmitter { // Handle process exit this.process.on('exit', (code: number) => { - console.log('Audio capture process exited with code:', code); this.emit('exit', code); this.process = null; }); @@ -240,8 +235,7 @@ export class SystemAudioCapture extends SimpleEventEmitter { // Attempt to restart if auto-restart is enabled if (this.autoRestartEnabled && !this.isRestarting) { - console.log('🔄 Auto-restarting due to heartbeat timeout'); - setTimeout(() => this.restart(), 1000); + setTimeout(() => this.restart(), 1000); } } } @@ -275,7 +269,6 @@ export class SystemAudioCapture extends SimpleEventEmitter { async restart(): Promise { if (this.isRestarting) { - console.log('Already restarting audio capture, skipping...'); return; } @@ -360,7 +353,6 @@ export class SystemAudioCapture extends SimpleEventEmitter { case 'heartbeat': this.lastHeartbeat = new Date(); - console.log('💓 Audio capture heartbeat received'); this.emit('heartbeat'); break; @@ -396,8 +388,7 @@ export class SystemAudioCapture extends SimpleEventEmitter { this.errorCount >= 3; if (shouldRestart) { - console.log('Auto-restarting audio capture due to error:', packet.message); - setTimeout(() => this.restart(), 1000); + setTimeout(() => this.restart(), 1000); } } break; @@ -448,7 +439,6 @@ export async function isSystemAudioAvailable(): Promise { try { fs.accessSync(execPath); } catch { - console.log('Swift executable not found at:', execPath); return false; } @@ -457,7 +447,6 @@ export async function isSystemAudioAvailable(): Promise { const majorVersion = parseInt(release.split('.')[0]); const isSupported = majorVersion >= 22; // macOS 13.0 corresponds to Darwin 22.x - console.log('macOS version check:', release, 'Supported:', isSupported); return isSupported; } catch (error) { diff --git a/resources/js/services/mockRealtimeData.ts b/resources/js/services/mockRealtimeData.ts new file mode 100644 index 0000000..4064a63 --- /dev/null +++ b/resources/js/services/mockRealtimeData.ts @@ -0,0 +1,354 @@ +import type { TranscriptGroup, CustomerInsight, ContextualCoaching } from '@/types/realtime'; + +// Mock conversation data +const mockConversationFlow = [ + { + role: 'system', + messages: ['Call connected'], + timestamp: Date.now() - 300000, // 5 minutes ago + }, + { + role: 'salesperson', + messages: ["Hi! Thanks for taking my call. I'm calling from TechSolutions. How are you doing today?"], + timestamp: Date.now() - 295000, + }, + { + role: 'customer', + messages: ["I'm doing well, thanks. What's this about?"], + timestamp: Date.now() - 290000, + }, + { + role: 'salesperson', + messages: ["I noticed your company has been growing rapidly. We help businesses like yours streamline their operations with our automation platform. Are you currently using any automation tools?"], + timestamp: Date.now() - 285000, + }, + { + role: 'customer', + messages: ["We use a few different tools, but honestly, they don't talk to each other very well. It's been a pain point for our team."], + timestamp: Date.now() - 280000, + }, + { + role: 'salesperson', + messages: ["I hear that a lot. Integration issues can really slow things down. What specific challenges are you facing with your current setup?"], + timestamp: Date.now() - 275000, + }, + { + role: 'customer', + messages: [ + "Well, our sales team uses one CRM, marketing uses a different platform, and our customer service is on another system entirely.", + "We spend hours every week just moving data between systems manually." + ], + timestamp: Date.now() - 270000, + }, + { + role: 'salesperson', + messages: ["That sounds frustrating and time-consuming. Our platform actually specializes in connecting disparate systems. We've helped companies reduce manual data entry by up to 80%."], + timestamp: Date.now() - 265000, + }, + { + role: 'customer', + messages: ["That's impressive. But we've looked at integration platforms before. They always seem too complex or too expensive for what we need."], + timestamp: Date.now() - 260000, + }, + { + role: 'salesperson', + messages: ["I understand your concern. What's your typical monthly volume for data transfers between systems?"], + timestamp: Date.now() - 255000, + }, + { + role: 'customer', + messages: ["Probably around 10,000 records across all systems. But it varies month to month."], + timestamp: Date.now() - 250000, + }, + { + role: 'salesperson', + messages: ["Based on that volume, you'd fit perfectly into our Growth tier, which is $299/month. That includes all the connectors you'd need plus our visual workflow builder."], + timestamp: Date.now() - 245000, + }, + { + role: 'customer', + messages: ["How long does implementation typically take? We can't afford a long disruption."], + timestamp: Date.now() - 240000, + }, + { + role: 'salesperson', + messages: ["Most clients are up and running within 2 weeks. We provide a dedicated onboarding specialist and can start with just one integration to prove the value."], + timestamp: Date.now() - 235000, + }, + { + role: 'customer', + messages: ["That's better than I expected. Can you send me some more information? I'd need to discuss this with my team."], + timestamp: Date.now() - 230000, + }, + { + role: 'salesperson', + messages: ["Absolutely! I can send you a detailed proposal today. Would it make sense to schedule a 30-minute demo for you and your team later this week?"], + timestamp: Date.now() - 225000, + }, + { + role: 'customer', + messages: ["Yes, that would be helpful. How about Thursday afternoon?"], + timestamp: Date.now() - 220000, + }, + { + role: 'salesperson', + messages: ["Thursday afternoon works great. I have slots at 2 PM or 3:30 PM. Which works better for you?"], + timestamp: Date.now() - 215000, + }, + { + role: 'customer', + messages: ["Let's do 2 PM. Send me the calendar invite."], + timestamp: Date.now() - 210000, + }, + { + role: 'salesperson', + messages: ["Perfect! I'll send that right after our call along with the proposal. Just to confirm, I have your email as john.smith@techcorp.com, correct?"], + timestamp: Date.now() - 205000, + }, + { + role: 'customer', + messages: ["That's correct. Looking forward to the demo."], + timestamp: Date.now() - 200000, + }, + { + role: 'salesperson', + messages: ["Excellent! One last thing - who else from your team should I include in the demo invite?"], + timestamp: Date.now() - 195000, + }, + { + role: 'customer', + messages: ["Include Sarah Chen, our CTO, and Mike Johnson from Operations. They'll need to sign off on any new integrations."], + timestamp: Date.now() - 190000, + }, + { + role: 'salesperson', + messages: ["Got it. I'll include them both. Thanks for your time today, John. I'm excited to show you how we can solve those integration challenges!"], + timestamp: Date.now() - 185000, + }, + { + role: 'customer', + messages: ["Thanks. Talk to you Thursday."], + timestamp: Date.now() - 180000, + }, + { + role: 'system', + messages: ['Call ended'], + timestamp: Date.now() - 175000, + }, +]; + +// Mock customer insights +const mockInsights: CustomerInsight[] = [ + { + id: '1', + type: 'pain_point', + content: 'Systems don\'t integrate well - manual data transfer between CRM, marketing, and customer service', + confidence: 0.95, + timestamp: Date.now() - 270000, + }, + { + id: '2', + type: 'objection', + content: 'Concerned about complexity and cost based on previous experiences', + confidence: 0.85, + timestamp: Date.now() - 260000, + }, + { + id: '3', + type: 'positive_signal', + content: 'Interested in reducing manual work - spending hours weekly on data entry', + confidence: 0.9, + timestamp: Date.now() - 265000, + }, + { + id: '4', + type: 'buying_signal', + content: 'Agreed to demo and wants to include decision makers (CTO and Operations)', + confidence: 0.92, + timestamp: Date.now() - 190000, + }, + { + id: '5', + type: 'concern', + content: 'Worried about implementation time and business disruption', + confidence: 0.88, + timestamp: Date.now() - 240000, + }, +]; + +// Mock coaching suggestions +const mockCoachingData: ContextualCoaching[] = [ + { + id: '1', + type: 'tip', + content: 'Great job uncovering the pain point! Now quantify the impact.', + priority: 'medium', + timestamp: Date.now() - 270000, + }, + { + id: '2', + type: 'suggestion', + content: 'Ask about their budget range to better qualify the opportunity.', + priority: 'high', + timestamp: Date.now() - 250000, + }, + { + id: '3', + type: 'warning', + content: 'Customer mentioned decision makers - make sure to understand the full buying process.', + priority: 'medium', + timestamp: Date.now() - 190000, + }, + { + id: '4', + type: 'positive', + content: 'Excellent close! You secured a demo with key stakeholders.', + priority: 'low', + timestamp: Date.now() - 185000, + }, +]; + +// Customer profile +const mockCustomerProfile = { + name: 'John Smith', + company: 'TechCorp Industries', + role: 'VP of Technology', + industry: 'Manufacturing', + employeeCount: '500-1000', + annualRevenue: '$50M-$100M', +}; + +// Performance metrics +const mockMetrics = { + talkRatio: 45, // Salesperson talked 45% of the time + sentiment: 'positive', + engagementScore: 78, + objectionsHandled: 2, + painPointsIdentified: 3, + nextStepsSecured: true, +}; + +class MockRealtimeDataService { + private conversationIndex = 0; + private isActive = false; + private updateInterval: NodeJS.Timeout | null = null; + + // Get initial state + getInitialState() { + return { + customerProfile: mockCustomerProfile, + metrics: mockMetrics, + insights: [], + coaching: [], + transcripts: [], + }; + } + + // Start mock conversation simulation + startMockConversation(onUpdate: (data: any) => void) { + this.isActive = true; + this.conversationIndex = 0; + + // Send initial state + onUpdate({ + type: 'initial', + data: this.getInitialState(), + }); + + // Simulate real-time updates + this.updateInterval = setInterval(() => { + if (!this.isActive || this.conversationIndex >= mockConversationFlow.length) { + this.stopMockConversation(); + return; + } + + const currentMessage = mockConversationFlow[this.conversationIndex]; + + // Send transcript update + onUpdate({ + type: 'transcript', + data: { + role: currentMessage.role, + messages: currentMessage.messages, + timestamp: currentMessage.timestamp, + }, + }); + + // Send related insights + const relatedInsights = mockInsights.filter( + insight => Math.abs(insight.timestamp - currentMessage.timestamp) < 10000 + ); + + relatedInsights.forEach(insight => { + setTimeout(() => { + onUpdate({ + type: 'insight', + data: insight, + }); + }, 500); + }); + + // Send related coaching + const relatedCoaching = mockCoachingData.filter( + coaching => Math.abs(coaching.timestamp - currentMessage.timestamp) < 10000 + ); + + relatedCoaching.forEach(coaching => { + setTimeout(() => { + onUpdate({ + type: 'coaching', + data: coaching, + }); + }, 1000); + }); + + // Update metrics periodically + if (this.conversationIndex % 5 === 0) { + onUpdate({ + type: 'metrics', + data: { + ...mockMetrics, + engagementScore: mockMetrics.engagementScore + Math.random() * 10 - 5, + talkRatio: mockMetrics.talkRatio + Math.random() * 10 - 5, + }, + }); + } + + this.conversationIndex++; + }, 3000); // New message every 3 seconds + } + + // Stop mock conversation + stopMockConversation() { + this.isActive = false; + if (this.updateInterval) { + clearInterval(this.updateInterval); + this.updateInterval = null; + } + } + + // Get all mock data at once (for testing UI without simulation) + getAllMockData() { + const transcripts: TranscriptGroup[] = mockConversationFlow.map((flow, index) => ({ + id: `group-${index}`, + role: flow.role as 'salesperson' | 'customer' | 'system', + messages: flow.messages.map((text, msgIndex) => ({ + id: `msg-${index}-${msgIndex}`, + text, + timestamp: flow.timestamp, + })), + startTime: flow.timestamp, + endTime: flow.timestamp + 1000, + })); + + return { + customerProfile: mockCustomerProfile, + metrics: mockMetrics, + insights: mockInsights, + coaching: mockCoachingData, + transcripts, + }; + } +} + +export const mockRealtimeDataService = new MockRealtimeDataService(); \ No newline at end of file diff --git a/resources/js/stores/openai.ts b/resources/js/stores/openai.ts new file mode 100644 index 0000000..9b7a34c --- /dev/null +++ b/resources/js/stores/openai.ts @@ -0,0 +1,162 @@ +import { defineStore } from 'pinia'; +import { + type RealtimeAgent, + type RealtimeSession, + type RealtimeSessionOptions, +} from '@openai/agents-realtime'; + +interface AgentInfo { + agent: RealtimeAgent | null; + session: RealtimeSession | null; + isConnected: boolean; +} + +export const useOpenAIStore = defineStore('openai', { + state: () => ({ + // Agent instances + salespersonAgent: null as AgentInfo | null, + coachAgent: null as AgentInfo | null, + + // API key + ephemeralKey: null as string | null, + keyExpiresAt: null as number | null, + + // Session configuration + sessionConfig: { + model: 'gpt-4o-mini-realtime-preview-2024-12-17', + instructions: '', + voice: 'verse' as const, + input_audio_format: 'pcm16' as const, + output_audio_format: 'pcm16' as const, + input_audio_transcription: { + model: 'gpt-4o-mini-transcribe', + }, + turn_detection: null, + tools: [] as any[], + tool_choice: 'auto' as const, + temperature: 0.7, + max_response_output_tokens: 4096, + } as RealtimeSessionOptions, + }), + + getters: { + isSalespersonConnected: (state) => state.salespersonAgent?.isConnected ?? false, + isCoachConnected: (state) => state.coachAgent?.isConnected ?? false, + isAnyAgentConnected: (state) => { + return (state.salespersonAgent?.isConnected ?? false) || + (state.coachAgent?.isConnected ?? false); + }, + hasValidKey: (state) => { + if (!state.ephemeralKey || !state.keyExpiresAt) return false; + return Date.now() < state.keyExpiresAt; + }, + }, + + actions: { + // Key management + setEphemeralKey(key: string, expiresIn: number) { + this.ephemeralKey = key; + this.keyExpiresAt = Date.now() + (expiresIn * 1000); + }, + + clearEphemeralKey() { + this.ephemeralKey = null; + this.keyExpiresAt = null; + }, + + // Agent management + setSalespersonAgent(agent: RealtimeAgent, session: RealtimeSession) { + this.salespersonAgent = { + agent, + session, + isConnected: false, + }; + }, + + setCoachAgent(agent: RealtimeAgent, session: RealtimeSession) { + this.coachAgent = { + agent, + session, + isConnected: false, + }; + }, + + updateAgentConnectionStatus(agentType: 'salesperson' | 'coach', connected: boolean) { + const agentInfo = agentType === 'salesperson' ? this.salespersonAgent : this.coachAgent; + if (agentInfo) { + agentInfo.isConnected = connected; + } + }, + + // Session configuration + updateSessionConfig(updates: Partial) { + Object.assign(this.sessionConfig, updates); + }, + + setSessionInstructions(instructions: string) { + this.sessionConfig.instructions = instructions; + }, + + setSessionTools(tools: any[]) { + this.sessionConfig.tools = tools; + }, + + // Cleanup + disconnectAllAgents() { + // Access raw objects to avoid Vue proxy issues + const rawSalespersonSession = this.salespersonAgent?.session; + const rawCoachSession = this.coachAgent?.session; + + if (rawSalespersonSession && typeof rawSalespersonSession.close === 'function') { + try { + rawSalespersonSession.close(); + } catch (error) { + console.error('Error closing salesperson session:', error); + } + } + + if (rawCoachSession && typeof rawCoachSession.close === 'function') { + try { + rawCoachSession.close(); + } catch (error) { + console.error('Error closing coach session:', error); + } + } + + // Update connection status + if (this.salespersonAgent) { + this.salespersonAgent.isConnected = false; + } + if (this.coachAgent) { + this.coachAgent.isConnected = false; + } + }, + + clearAgents() { + this.salespersonAgent = null; + this.coachAgent = null; + }, + + reset() { + this.disconnectAllAgents(); + this.clearAgents(); + this.clearEphemeralKey(); + // Reset session config to defaults + this.sessionConfig = { + model: 'gpt-4o-mini-realtime-preview-2024-12-17', + instructions: '', + voice: 'verse', + input_audio_format: 'pcm16', + output_audio_format: 'pcm16', + input_audio_transcription: { + model: 'gpt-4o-mini-transcribe', + }, + turn_detection: null, + tools: [], + tool_choice: 'auto', + temperature: 0.7, + max_response_output_tokens: 4096, + }; + }, + }, +}); \ No newline at end of file diff --git a/resources/js/stores/realtimeAgent.ts b/resources/js/stores/realtimeAgent.ts new file mode 100644 index 0000000..19eb0e7 --- /dev/null +++ b/resources/js/stores/realtimeAgent.ts @@ -0,0 +1,424 @@ +import { defineStore } from 'pinia'; +import type { + ActionItem, + Commitment, + CustomerInfo, + CustomerIntelligence, + Insight, + Template, + Topic, + TranscriptGroup +} from '@/types/realtimeAgent'; +import { mockRealtimeDataService } from '@/services/mockRealtimeData'; +import type { CustomerInsight, ContextualCoaching } from '@/types/realtime'; + +export const useRealtimeAgentStore = defineStore('realtimeAgent', { + state: () => ({ + // Connection + connectionStatus: 'disconnected' as 'disconnected' | 'connecting' | 'connected', + isActive: false, + isMockMode: false, + mockInterval: null as NodeJS.Timeout | null, + + // Templates + selectedTemplate: null as Template | null, + templates: [] as Template[], + + // Transcript + transcriptGroups: [] as TranscriptGroup[], + + // Intelligence + customerIntelligence: { + intent: 'unknown', + buyingStage: 'Discovery', + engagementLevel: 50, + sentiment: 'neutral', + } as CustomerIntelligence, + insights: [] as Insight[], + topics: [] as Topic[], + + // Actions + commitments: [] as Commitment[], + actionItems: [] as ActionItem[], + + // Coaching + coachingItems: [] as ContextualCoaching[], + + // UI State + coveredPoints: [] as number[], + showCustomerModal: false, + customerInfo: { + name: '', + company: '', + } as CustomerInfo, + + // Audio + audioLevel: 0, + systemAudioLevel: 0, + microphoneStatus: 'inactive', + isSystemAudioActive: false, + + // Other + conversationContext: '', + lastCustomerMessage: '', + intelligenceUpdating: false, + }), + + getters: { + talkingPointsProgress: (state) => { + if (!state.selectedTemplate?.talking_points?.length) return 0; + return Math.round((state.coveredPoints.length / state.selectedTemplate.talking_points.length) * 100); + }, + + recentInsights: (state) => { + return state.insights.slice(0, 5); + }, + + hasActiveCall: (state) => { + return state.isActive && state.connectionStatus === 'connected'; + }, + + sortedTopics: (state) => { + return [...state.topics].sort((a, b) => b.mentions - a.mentions); + }, + }, + + actions: { + // Connection Actions + setConnectionStatus(status: 'disconnected' | 'connecting' | 'connected') { + this.connectionStatus = status; + }, + + setActiveState(active: boolean) { + this.isActive = active; + }, + + // Template Actions + setSelectedTemplate(template: Template | null) { + this.selectedTemplate = template; + if (template) { + this.coveredPoints = []; + } + }, + + setTemplates(templates: Template[]) { + this.templates = templates; + }, + + // Transcript Actions + addTranscriptGroup(group: TranscriptGroup) { + this.transcriptGroups.push(group); + }, + + appendToLastTranscriptGroup(role: string, text: string, maxTimeGapMs: number = 5000): boolean { + // Check if we have any transcript groups + if (this.transcriptGroups.length === 0) { + return false; + } + + // Get the last transcript group + const lastGroup = this.transcriptGroups[this.transcriptGroups.length - 1]; + + // Check if it's the same speaker and within time window + if (lastGroup.role === role) { + const now = Date.now(); + const lastMessageTime = lastGroup.messages[lastGroup.messages.length - 1]?.timestamp || lastGroup.startTime; + + if (now - lastMessageTime <= maxTimeGapMs) { + // Append to existing group + lastGroup.messages.push({ text, timestamp: now }); + lastGroup.endTime = now; + return true; + } + } + + return false; + }, + + updateTranscriptGroup(groupId: string, updates: Partial) { + const group = this.transcriptGroups.find(g => g.id === groupId); + if (group) { + Object.assign(group, updates); + } + }, + + clearTranscript() { + this.transcriptGroups = []; + }, + + // Topic Actions + trackDiscussionTopic(name: string, sentiment: string, context?: string) { + const normalizedName = name.trim().toLowerCase(); + const existingTopic = this.topics.find(t => t.name.toLowerCase() === normalizedName); + + if (existingTopic) { + existingTopic.mentions++; + existingTopic.lastMentioned = Date.now(); + if (sentiment && sentiment !== existingTopic.sentiment) { + existingTopic.sentiment = 'mixed'; + } + } else { + this.topics.push({ + id: `topic-${Date.now()}`, + name, + sentiment: sentiment as any, + mentions: 1, + lastMentioned: Date.now(), + context, + }); + } + }, + + // Customer Intelligence Actions + updateCustomerIntelligence(updates: Partial) { + this.intelligenceUpdating = true; + Object.assign(this.customerIntelligence, updates); + setTimeout(() => { + this.intelligenceUpdating = false; + }, 500); + }, + + // Insight Actions + addKeyInsight(type: string, text: string, importance: string) { + this.insights.unshift({ + id: `insight-${Date.now()}`, + type: type as any, + text, + importance: importance as any, + timestamp: Date.now(), + }); + + // Keep only last 20 insights + if (this.insights.length > 20) { + this.insights.pop(); + } + }, + + // Commitment Actions + captureCommitment(speaker: string, text: string, type: string, deadline?: string) { + this.commitments.push({ + id: `commitment-${Date.now()}`, + speaker: speaker as any, + text, + type: type as any, + deadline, + timestamp: Date.now(), + }); + }, + + // Action Item Actions + addActionItem(text: string, owner: string, type: string, deadline?: string, relatedCommitment?: string) { + this.actionItems.push({ + id: `action-${Date.now()}`, + text, + owner: owner as any, + type: type as any, + completed: false, + deadline, + relatedCommitment, + }); + }, + + toggleActionItemComplete(id: string) { + const item = this.actionItems.find(a => a.id === id); + if (item) { + item.completed = !item.completed; + } + }, + + // Talking Points Actions + toggleTalkingPoint(index: number) { + const idx = this.coveredPoints.indexOf(index); + if (idx > -1) { + this.coveredPoints.splice(idx, 1); + } else { + this.coveredPoints.push(index); + } + }, + + // Audio Actions + setAudioLevel(level: number) { + this.audioLevel = level; + }, + + setSystemAudioLevel(level: number) { + this.systemAudioLevel = level; + }, + + setMicrophoneStatus(status: string) { + this.microphoneStatus = status; + }, + + setSystemAudioActive(active: boolean) { + this.isSystemAudioActive = active; + }, + + // Customer Info Actions + setCustomerInfo(info: CustomerInfo) { + this.customerInfo = info; + }, + + setShowCustomerModal(show: boolean) { + this.showCustomerModal = show; + }, + + // Context Actions + setConversationContext(context: string) { + this.conversationContext = context; + }, + + setLastCustomerMessage(message: string) { + this.lastCustomerMessage = message; + }, + + // Coaching Actions + addCoachingItem(coaching: ContextualCoaching) { + this.coachingItems.unshift(coaching); + // Keep only last 10 coaching items + if (this.coachingItems.length > 10) { + this.coachingItems.pop(); + } + }, + + // Reset Actions + resetSession() { + // Keep templates and selected template + const { templates, selectedTemplate } = this; + + // Reset everything else + this.$reset(); + + // Restore templates + this.templates = templates; + this.selectedTemplate = selectedTemplate; + }, + + resetIntelligence() { + this.customerIntelligence = { + intent: 'unknown', + buyingStage: 'Discovery', + engagementLevel: 50, + sentiment: 'neutral', + }; + this.insights = []; + this.topics = []; + this.commitments = []; + this.actionItems = []; + this.conversationContext = ''; + this.lastCustomerMessage = ''; + }, + + // Mock Data Actions + enableMockMode() { + this.isMockMode = true; + this.resetSession(); + + // Load all mock data at once + const mockData = mockRealtimeDataService.getAllMockData(); + + // Set connection status + this.connectionStatus = 'connected'; + this.isActive = true; + + // Set customer info + this.customerInfo = { + name: mockData.customerProfile.name, + company: mockData.customerProfile.company, + }; + + // Load transcripts + mockData.transcripts.forEach(transcript => { + this.addTranscriptGroup(transcript); + }); + + // Load insights + mockData.insights.forEach(insight => { + this.addKeyInsight( + insight.type, + insight.content, + insight.confidence > 0.9 ? 'high' : insight.confidence > 0.7 ? 'medium' : 'low' + ); + }); + + // Update customer intelligence + this.updateCustomerIntelligence({ + intent: 'evaluation', + buyingStage: 'Discovery', + engagementLevel: mockData.metrics.engagementScore, + sentiment: mockData.metrics.sentiment, + }); + + // Add some topics + this.trackDiscussionTopic('Integration Issues', 'negative', 'Systems don\'t talk to each other'); + this.trackDiscussionTopic('Manual Data Entry', 'negative', 'Spending hours on manual work'); + this.trackDiscussionTopic('Automation Platform', 'positive', 'Interested in our solution'); + this.trackDiscussionTopic('Demo Scheduled', 'positive', 'Thursday 2 PM demo'); + + // Add action items + this.addActionItem('Send proposal and demo invite', 'salesperson', 'followup', 'Today'); + this.addActionItem('Include Sarah Chen (CTO) in demo', 'salesperson', 'meeting'); + this.addActionItem('Include Mike Johnson (Operations) in demo', 'salesperson', 'meeting'); + + // Add commitments + this.captureCommitment('salesperson', 'Send detailed proposal today', 'deadline', 'Today'); + this.captureCommitment('salesperson', 'Schedule 30-minute demo for Thursday 2 PM', 'meeting', 'Thursday'); + this.captureCommitment('customer', 'Review proposal with team', 'review'); + + // Add coaching items + mockData.coaching.forEach(coaching => { + this.addCoachingItem(coaching); + }); + }, + + disableMockMode() { + this.isMockMode = false; + if (this.mockInterval) { + mockRealtimeDataService.stopMockConversation(); + this.mockInterval = null; + } + this.resetSession(); + }, + + startMockSimulation() { + if (!this.isMockMode) return; + + this.resetSession(); + this.connectionStatus = 'connected'; + this.isActive = true; + + mockRealtimeDataService.startMockConversation((update) => { + switch (update.type) { + case 'transcript': + const group: TranscriptGroup = { + id: `group-${Date.now()}`, + role: update.data.role, + messages: update.data.messages.map((text: string, index: number) => ({ + id: `msg-${Date.now()}-${index}`, + text, + timestamp: update.data.timestamp, + })), + startTime: update.data.timestamp, + endTime: update.data.timestamp + 1000, + }; + this.addTranscriptGroup(group); + break; + + case 'insight': + const insight = update.data as CustomerInsight; + this.addKeyInsight( + insight.type, + insight.content, + insight.confidence > 0.9 ? 'high' : 'medium' + ); + break; + + case 'metrics': + this.updateCustomerIntelligence({ + engagementLevel: Math.max(0, Math.min(100, update.data.engagementScore)), + }); + break; + } + }); + }, + }, +}); \ No newline at end of file diff --git a/resources/js/stores/settings.ts b/resources/js/stores/settings.ts new file mode 100644 index 0000000..43af370 --- /dev/null +++ b/resources/js/stores/settings.ts @@ -0,0 +1,101 @@ +import { defineStore } from 'pinia'; + +export const useSettingsStore = defineStore('settings', { + state: () => ({ + // Protection & Overlay + isProtectionEnabled: false, + isProtectionSupported: false, + isOverlayMode: false, + isOverlaySupported: true, + + // UI State + showCoachDropdown: false, + showMobileMenu: false, + templateSearchQuery: '', + + // Feature flags + debugMode: false, + }), + + getters: { + protectionStatusText: (state) => { + if (!state.isProtectionSupported) return 'N/A'; + return state.isProtectionEnabled ? 'Protected' : 'Protect'; + }, + + overlayStatusText: (state) => { + return state.isOverlayMode ? 'Normal' : 'Overlay'; + }, + }, + + actions: { + toggleProtection() { + if (this.isProtectionSupported) { + this.isProtectionEnabled = !this.isProtectionEnabled; + } + }, + + toggleOverlayMode() { + this.isOverlayMode = !this.isOverlayMode; + // Add class to body element + if (this.isOverlayMode) { + document.body.classList.add('overlay-mode-active'); + } else { + document.body.classList.remove('overlay-mode-active'); + } + }, + + setOverlayMode(enabled: boolean) { + this.isOverlayMode = enabled; + // Add class to body element + if (this.isOverlayMode) { + document.body.classList.add('overlay-mode-active'); + } else { + document.body.classList.remove('overlay-mode-active'); + } + }, + + setProtectionSupported(supported: boolean) { + this.isProtectionSupported = supported; + }, + + setOverlaySupported(supported: boolean) { + this.isOverlaySupported = supported; + }, + + toggleCoachDropdown() { + this.showCoachDropdown = !this.showCoachDropdown; + // Close mobile menu if open + if (this.showCoachDropdown) { + this.showMobileMenu = false; + } + }, + + toggleMobileMenu() { + this.showMobileMenu = !this.showMobileMenu; + // Close coach dropdown if open + if (this.showMobileMenu) { + this.showCoachDropdown = false; + } + }, + + closeAllDropdowns() { + this.showCoachDropdown = false; + this.showMobileMenu = false; + }, + + setTemplateSearchQuery(query: string) { + this.templateSearchQuery = query; + }, + + resetDropdownStates() { + this.showCoachDropdown = false; + this.showMobileMenu = false; + this.templateSearchQuery = ''; + }, + + setDebugMode(enabled: boolean) { + this.debugMode = enabled; + }, + }, +}); \ No newline at end of file diff --git a/resources/js/test-audio-loopback.md b/resources/js/test-audio-loopback.md new file mode 100644 index 0000000..b79a562 --- /dev/null +++ b/resources/js/test-audio-loopback.md @@ -0,0 +1,71 @@ +# Testing Audio Loopback Extension + +This document explains how to test the audio loopback functionality that's now implemented via the extension system. + +## Components Created + +### 1. IPC Handlers (in `nativephp-extension.js`) +- `enable-loopback-audio` - Enables audio loopback for a device +- `disable-loopback-audio` - Disables audio loopback + +### 2. Window APIs (in `nativephp-preload.js`) +- `window.audioLoopback.enableLoopback(deviceId)` - Enable loopback +- `window.audioLoopback.disableLoopback()` - Disable loopback +- `window.extensionTest.ping()` - Test the extension system +- `window.extensionTest.getInfo()` - Get extension information + +## Testing from Browser Console + +Once the app is running with `composer native:dev`, open the developer tools in the Electron app and try: + +```javascript +// Test if extension system is working +await window.extensionTest.ping(); +// Should return: { success: true, message: 'Pong!...', receivedArgs: ['from-preload'] } + +// Get extension info +await window.extensionTest.getInfo(); +// Should return: { success: true, info: { extensionVersion: '1.0.0', ... } } + +// Enable audio loopback +await window.audioLoopback.enableLoopback('default'); +// Should return: { success: true, deviceId: 'default', message: 'Audio loopback enabled (mock implementation)' } + +// Disable audio loopback +await window.audioLoopback.disableLoopback(); +// Should return: { success: true, message: 'Audio loopback disabled (mock implementation)' } +``` + +## What to Look For in Console Logs + +When testing, you should see these logs: + +1. **Extension Loading**: + - `[NativePHP] Loading extensions from app path: /path/to/project` + - `[NativePHP] Loaded extension from: /path/to/project/resources/js/nativephp-extension.js` + - `[NativePHP] Exposed preload API: audioLoopback` + - `[NativePHP] Exposed preload API: extensionTest` + +2. **IPC Handler Registration**: + - `[NativePHP] Registered IPC handler: enable-loopback-audio` + - `[NativePHP] Registered IPC handler: disable-loopback-audio` + +3. **When Calling Functions**: + - `[Preload Extension] Calling enable-loopback-audio` + - `[Extension Test] IPC handler enable-loopback-audio called` + +## Important Notes + +1. **Path Resolution Issue**: Currently, the preload loader uses `process.cwd()` which might not resolve to the correct path. If the preload APIs aren't available, you may need to temporarily copy `nativephp-preload.js` to the package directory. + +2. **Mock Implementation**: The current implementation is a mock. The actual audio loopback logic (using electron-audio-loopback) would need to be implemented in the IPC handlers. + +3. **Error Handling**: If you see errors about `window.audioLoopback` being undefined, it means the preload extension didn't load properly. + +## Next Steps + +To implement actual audio loopback functionality: +1. Import and use electron-audio-loopback methods in the IPC handlers +2. Handle device selection and audio routing +3. Add error handling for permission issues +4. Implement proper cleanup in the disable handler \ No newline at end of file diff --git a/resources/js/test-extension.md b/resources/js/test-extension.md new file mode 100644 index 0000000..f6178e4 --- /dev/null +++ b/resources/js/test-extension.md @@ -0,0 +1,59 @@ +# Testing NativePHP Extension + +This document shows how to test the extension system. + +## Console Logging + +When you run `composer native:dev`, you should see the following logs in the console: + +1. `[Extension Test] beforeReady hook called` - Before app initialization +2. `[Extension Test] afterReady hook called` - After app is ready +3. `[Extension Test] beforeQuit hook called` - When closing the app + +## Testing IPC Handlers + +From the renderer process (in Vue components or browser console), you can test: + +```javascript +// Test ping +const result = await window.Native.invoke('test:ping', 'arg1', 'arg2'); +console.log(result); // { success: true, message: 'Pong!...', receivedArgs: ['arg1', 'arg2'] } + +// Test echo +const echoResult = await window.Native.invoke('test:echo', 'Hello Extension!'); +console.log(echoResult); // { success: true, echo: 'Hello Extension!', processedAt: '...' } + +// Test get info +const info = await window.Native.invoke('test:get-info'); +console.log(info); // { success: true, info: { extensionVersion: '1.0.0', ... } } +``` + +## Testing API Routes + +From Laravel controllers or using curl: + +```bash +# Test GET status +curl http://127.0.0.1:4000/api/test/status \ + -H "X-NativePHP-Secret: [secret]" + +# Test POST echo +curl -X POST http://127.0.0.1:4000/api/test/echo \ + -H "Content-Type: application/json" \ + -H "X-NativePHP-Secret: [secret]" \ + -d '{"message": "Hello from Laravel!"}' + +# Test GET with params +curl http://127.0.0.1:4000/api/test/info/myParam?query=value \ + -H "X-NativePHP-Secret: [secret]" +``` + +Note: The API port (4000) and secret will be different for each session. Check the console logs for the actual port. + +## Verification Steps + +1. Run `composer native:dev` +2. Check console for extension loading message: `[NativePHP] Loaded extension from: .../resources/js/nativephp-extension.js` +3. Check for lifecycle hooks being called +4. Open the app and test IPC handlers from browser console +5. Test API routes using curl or from Laravel code \ No newline at end of file diff --git a/resources/js/types/openai-agents.d.ts b/resources/js/types/openai-agents.d.ts new file mode 100644 index 0000000..542171c --- /dev/null +++ b/resources/js/types/openai-agents.d.ts @@ -0,0 +1,69 @@ +declare module '@openai/agents' { + export interface AgentOptions { + name: string; + instructions?: string; + modalities?: string[]; + tools?: any[]; + turnDetection?: TurnDetectionOptions; + } + + export interface TurnDetectionOptions { + type: 'server_vad' | 'none'; + threshold?: number; + prefix_padding_ms?: number; + silence_duration_ms?: number; + } + + export interface AgentEvent { + type: string; + data?: any; + } + + export class RealtimeAgent { + constructor(options: AgentOptions); + + connect(transport: any): Promise; + disconnect(): Promise; + + on(event: 'transcription', handler: (transcript: string) => void): void; + on(event: 'function_call', handler: (data: { name: string; arguments: any }) => void): void; + on(event: 'error', handler: (error: any) => void): void; + on(event: string, handler: (data: any) => void): void; + + off(event: string, handler?: (...args: any[]) => void): void; + + sendAudio(audio: ArrayBuffer | Int16Array): void; + } +} + +declare module '@openai/agents/realtime' { + export { RealtimeAgent } from '@openai/agents'; +} + +declare module '@openai/agents/realtime/websocket' { + export interface WebSocketRealtimeTransportOptions { + url: string; + model: string; + apiKey: string; + headers?: Record; + } + + export class WebSocketRealtimeTransport { + constructor(options: WebSocketRealtimeTransportOptions); + + connect(): Promise; + close(): void; + + isConnected(): boolean; + + send(data: any): void; + + on(event: string, handler: (...args: any[]) => void): void; + off(event: string, handler?: (...args: any[]) => void): void; + } +} + +declare module '@openai/agents-realtime' { + export * from '@openai/agents/realtime'; + export * from '@openai/agents/realtime/websocket'; +} \ No newline at end of file diff --git a/resources/js/types/realtime.ts b/resources/js/types/realtime.ts new file mode 100644 index 0000000..e6c0aa2 --- /dev/null +++ b/resources/js/types/realtime.ts @@ -0,0 +1,49 @@ +// Realtime Agent Types + +export interface TranscriptMessage { + id: string; + text: string; + timestamp: number; +} + +export interface TranscriptGroup { + id: string; + role: 'salesperson' | 'customer' | 'system'; + messages: TranscriptMessage[]; + startTime: number; + endTime: number; +} + +export interface CustomerInsight { + id: string; + type: 'pain_point' | 'objection' | 'positive_signal' | 'buying_signal' | 'concern'; + content: string; + confidence: number; + timestamp: number; +} + +export interface ContextualCoaching { + id: string; + type: 'tip' | 'suggestion' | 'warning' | 'positive'; + content: string; + priority: 'high' | 'medium' | 'low'; + timestamp: number; +} + +export interface CustomerProfile { + name: string; + company: string; + role: string; + industry: string; + employeeCount: string; + annualRevenue: string; +} + +export interface PerformanceMetrics { + talkRatio: number; + sentiment: string; + engagementScore: number; + objectionsHandled: number; + painPointsIdentified: number; + nextStepsSecured: boolean; +} \ No newline at end of file diff --git a/resources/js/types/realtimeAgent.ts b/resources/js/types/realtimeAgent.ts new file mode 100644 index 0000000..ae28bbc --- /dev/null +++ b/resources/js/types/realtimeAgent.ts @@ -0,0 +1,66 @@ +export interface Template { + id: string; + name: string; + prompt: string; + created_at: string; + is_system?: boolean; + icon?: string; + talking_points?: string[]; +} + +export interface TranscriptGroup { + id: string; + role: 'salesperson' | 'customer' | 'system'; + messages: Array<{ text: string; timestamp: number }>; + startTime: number; + endTime?: number; + systemCategory?: 'error' | 'warning' | 'info' | 'success'; +} + +export interface CustomerIntelligence { + intent: 'research' | 'evaluation' | 'decision' | 'implementation' | 'unknown'; + buyingStage: string; + engagementLevel: number; + sentiment: 'positive' | 'negative' | 'neutral'; +} + +export interface Insight { + id: string; + type: 'pain_point' | 'objection' | 'positive_signal' | 'concern' | 'question'; + text: string; + importance: 'high' | 'medium' | 'low'; + timestamp: number; +} + +export interface Topic { + id: string; + name: string; + sentiment: 'positive' | 'negative' | 'neutral' | 'mixed'; + mentions: number; + lastMentioned: number; + context?: string; +} + +export interface Commitment { + id: string; + speaker: 'salesperson' | 'customer'; + text: string; + type: 'promise' | 'next_step' | 'deliverable'; + deadline?: string; + timestamp: number; +} + +export interface ActionItem { + id: string; + text: string; + owner: 'salesperson' | 'customer' | 'both'; + type: 'follow_up' | 'send_info' | 'schedule' | 'internal'; + completed: boolean; + deadline?: string; + relatedCommitment?: string; +} + +export interface CustomerInfo { + name: string; + company: string; +} \ No newline at end of file diff --git a/resources/js/utils/electronPermissions.ts b/resources/js/utils/electronPermissions.ts index fb94393..2a383f5 100644 --- a/resources/js/utils/electronPermissions.ts +++ b/resources/js/utils/electronPermissions.ts @@ -32,14 +32,6 @@ export async function checkMicrophoneAvailability(): Promise<{ available: boolea const devices = await navigator.mediaDevices.enumerateDevices(); const audioInputs = devices.filter((device) => device.kind === 'audioinput'); - console.log( - 'Available audio input devices:', - audioInputs.map((d) => ({ - deviceId: d.deviceId, - label: d.label || 'Unnamed Device', - groupId: d.groupId, - })), - ); if (audioInputs.length === 0) { return { available: false, message: 'No microphone devices found' }; @@ -67,19 +59,16 @@ export async function selectBestMicrophone(devices: MediaDeviceInfo[]): Promise< const externalMic = nonDefaultDevices.find((d) => d.label.toLowerCase().includes('usb') || d.label.toLowerCase().includes('external')); if (externalMic) { - console.log('Selected external microphone:', externalMic.label); return externalMic.deviceId; } // Use first non-default device if (nonDefaultDevices.length > 0) { - console.log('Selected microphone:', nonDefaultDevices[0].label); return nonDefaultDevices[0].deviceId; } // Fallback to first available if (devices.length > 0) { - console.log('Using default microphone:', devices[0].label); return devices[0].deviceId; } diff --git a/routes/web.php b/routes/web.php index 83fecc4..59505ec 100644 --- a/routes/web.php +++ b/routes/web.php @@ -10,25 +10,27 @@ if (Auth::check()) { return redirect()->route('dashboard'); } - + return Inertia::render('Welcome'); })->name('home'); -// Onboarding Route -Route::get('/onboarding', function () { - return Inertia::render('Onboarding'); -})->name('onboarding'); - Route::get('dashboard', function () { return Inertia::render('Dashboard'); })->name('dashboard'); - // NativePHP Desktop Routes Route::get('/realtime-agent', function () { return Inertia::render('RealtimeAgent/Main'); })->name('realtime-agent'); +// Realtime Agent V2 - OpenAI Agents SDK Implementation +Route::get('/realtime-agent-v2', function () { + return Inertia::render('RealtimeAgent/MainV2'); +})->name('realtime-agent-v2'); + +// Audio Test Route - Testing electron-audio-loopback +Route::get('/audio-test', [\App\Http\Controllers\AudioTestController::class, 'index']) + ->name('audio-test'); // Realtime API Routes Route::post('/api/realtime/ephemeral-key', [\App\Http\Controllers\RealtimeController::class, 'generateEphemeralKey']) @@ -37,6 +39,7 @@ // API Key Status Route Route::get('/api/openai/status', function () { $apiKeyService = app(\App\Services\ApiKeyService::class); + return response()->json([ 'hasApiKey' => $apiKeyService->hasApiKey(), ]); @@ -45,15 +48,15 @@ // Open external URL in default browser (for NativePHP) Route::post('/api/open-external', function (\Illuminate\Http\Request $request) { $url = $request->input('url'); - + // Validate URL - if (!filter_var($url, FILTER_VALIDATE_URL)) { + if (! filter_var($url, FILTER_VALIDATE_URL)) { return response()->json(['error' => 'Invalid URL'], 400); } - + // Use NativePHP Shell to open in default browser \Native\Laravel\Facades\Shell::openExternal($url); - + return response()->json(['success' => true]); })->name('api.open-external'); diff --git a/tests/Feature/ApiKeyStoreTest.php b/tests/Feature/ApiKeyStoreTest.php index 393d6e6..c42a562 100644 --- a/tests/Feature/ApiKeyStoreTest.php +++ b/tests/Feature/ApiKeyStoreTest.php @@ -8,7 +8,6 @@ class ApiKeyStoreTest extends TestCase { - protected function setUp(): void { parent::setUp(); @@ -37,14 +36,14 @@ public function test_stores_valid_api_key_successfully(): void ->with($validApiKey); $response = $this->postJson('/api/openai/api-key', [ - 'api_key' => $validApiKey + 'api_key' => $validApiKey, ]); $response->assertStatus(200) - ->assertJson([ - 'success' => true, - 'message' => 'API key saved successfully.' - ]); + ->assertJson([ + 'success' => true, + 'message' => 'API key saved successfully.', + ]); } public function test_rejects_invalid_api_key(): void @@ -62,14 +61,14 @@ public function test_rejects_invalid_api_key(): void $mockApiKeyService->shouldNotReceive('setApiKey'); $response = $this->postJson('/api/openai/api-key', [ - 'api_key' => $invalidApiKey + 'api_key' => $invalidApiKey, ]); $response->assertStatus(422) - ->assertJson([ - 'success' => false, - 'message' => 'The provided API key is invalid. Please check and try again.' - ]); + ->assertJson([ + 'success' => false, + 'message' => 'The provided API key is invalid. Please check and try again.', + ]); } public function test_validates_required_api_key_field(): void @@ -77,27 +76,27 @@ public function test_validates_required_api_key_field(): void $response = $this->postJson('/api/openai/api-key', []); $response->assertStatus(422) - ->assertJsonValidationErrors(['api_key']); + ->assertJsonValidationErrors(['api_key']); } public function test_validates_api_key_minimum_length(): void { $response = $this->postJson('/api/openai/api-key', [ - 'api_key' => 'sk-short' + 'api_key' => 'sk-short', ]); $response->assertStatus(422) - ->assertJsonValidationErrors(['api_key']); + ->assertJsonValidationErrors(['api_key']); } public function test_validates_api_key_is_string(): void { $response = $this->postJson('/api/openai/api-key', [ - 'api_key' => 123456 + 'api_key' => 123456, ]); $response->assertStatus(422) - ->assertJsonValidationErrors(['api_key']); + ->assertJsonValidationErrors(['api_key']); } public function test_handles_api_key_service_exception(): void @@ -113,7 +112,7 @@ public function test_handles_api_key_service_exception(): void ->andThrow(new \Exception('Service error')); $response = $this->postJson('/api/openai/api-key', [ - 'api_key' => $validApiKey + 'api_key' => $validApiKey, ]); // Controller doesn't handle exceptions, so it returns 500 @@ -124,7 +123,7 @@ public function test_api_key_endpoint_accessible_without_existing_api_key(): voi { // This test ensures the middleware allows access to the API key store endpoint // even when no API key is configured - + $mockApiKeyService = Mockery::mock(ApiKeyService::class); $this->app->instance(ApiKeyService::class, $mockApiKeyService); @@ -138,9 +137,9 @@ public function test_api_key_endpoint_accessible_without_existing_api_key(): voi ->once(); $response = $this->postJson('/api/openai/api-key', [ - 'api_key' => $validApiKey + 'api_key' => $validApiKey, ]); $response->assertStatus(200); } -} \ No newline at end of file +} diff --git a/tests/Feature/Controllers/ConversationControllerTest.php b/tests/Feature/Controllers/ConversationControllerTest.php index 0764cb6..0ae8fc5 100644 --- a/tests/Feature/Controllers/ConversationControllerTest.php +++ b/tests/Feature/Controllers/ConversationControllerTest.php @@ -2,7 +2,6 @@ use App\Models\ConversationSession; use App\Models\ConversationTranscript; -use App\Models\ConversationInsight; use App\Services\ApiKeyService; beforeEach(function () { @@ -10,7 +9,7 @@ $mockApiKeyService = Mockery::mock(ApiKeyService::class); $mockApiKeyService->shouldReceive('hasApiKey')->andReturn(true); $this->app->instance(ApiKeyService::class, $mockApiKeyService); - + // Create a test conversation session for some tests $this->session = ConversationSession::create([ 'user_id' => null, @@ -25,9 +24,9 @@ test('can view conversations index page', function () { // Create some test sessions ConversationSession::factory()->count(5)->create(); - + $response = $this->get('/conversations'); - + $response->assertStatus(200) ->assertInertia(fn ($page) => $page ->component('Conversations/Index') @@ -44,12 +43,12 @@ ConversationSession::create(['user_id' => null, 'started_at' => now()->subDays(3)]); ConversationSession::create(['user_id' => null, 'started_at' => now()->subDays(1)]); ConversationSession::create(['user_id' => null, 'started_at' => now()->subDays(2)]); - + $response = $this->get('/conversations'); - + $response->assertStatus(200); $sessions = $response->original->getData()['page']['props']['sessions']['data']; - + // Check ordering expect($sessions[0]['started_at'])->toBeGreaterThan($sessions[1]['started_at']); expect($sessions[1]['started_at'])->toBeGreaterThan($sessions[2]['started_at']); @@ -64,15 +63,15 @@ 'spoken_at' => now(), 'order_index' => 1, ]); - + $this->session->insights()->create([ 'insight_type' => 'key_insight', 'data' => ['text' => 'Customer interested in product'], 'captured_at' => now(), ]); - + $response = $this->get("/conversations/{$this->session->id}"); - + $response->assertStatus(200) ->assertInertia(fn ($page) => $page ->component('Conversations/Show') @@ -84,7 +83,7 @@ test('show returns 404 for non-existent session', function () { $response = $this->get('/conversations/999999'); - + $response->assertStatus(404); }); @@ -95,7 +94,7 @@ 'customer_name' => 'Jane Smith', 'customer_company' => 'Tech Corp', ]); - + $response->assertStatus(200) ->assertJsonStructure([ 'session_id', @@ -104,7 +103,7 @@ ->assertJson([ 'message' => 'Session started successfully', ]); - + $this->assertDatabaseHas('conversation_sessions', [ 'template_used' => 'sales_call', 'customer_name' => 'Jane Smith', @@ -115,9 +114,9 @@ test('can start conversation session without optional fields', function () { $response = $this->postJson('/conversations'); - + $response->assertStatus(200); - + $this->assertDatabaseHas('conversation_sessions', [ 'user_id' => null, 'template_used' => null, @@ -135,14 +134,14 @@ ['insight_type' => 'commitment', 'data' => ['text' => 'Test'], 'captured_at' => now()], ['insight_type' => 'action_item', 'data' => ['text' => 'Test'], 'captured_at' => now()], ]); - + $this->session->transcripts()->create([ 'speaker' => 'salesperson', 'text' => 'Test transcript', 'spoken_at' => now(), 'order_index' => 1, ]); - + $response = $this->postJson("/conversations/{$this->session->id}/end", [ 'duration_seconds' => 300, 'final_intent' => 'high', @@ -151,14 +150,14 @@ 'final_sentiment' => 'positive', 'ai_summary' => 'Great conversation with customer.', ]); - + $response->assertStatus(200) ->assertJson([ 'message' => 'Session ended successfully', ]); - + $this->session->refresh(); - + expect($this->session->ended_at)->not->toBeNull(); expect($this->session->duration_seconds)->toBe(300); expect($this->session->final_intent)->toBe('high'); @@ -177,7 +176,7 @@ $response = $this->postJson("/conversations/{$this->session->id}/end", [ 'final_intent' => 'high', ]); - + $response->assertStatus(422) ->assertJsonValidationErrors(['duration_seconds']); }); @@ -191,7 +190,7 @@ 'group_id' => 'group-123', 'system_category' => 'greeting', ]); - + $response->assertStatus(200) ->assertJsonStructure([ 'transcript_id', @@ -200,7 +199,7 @@ ->assertJson([ 'message' => 'Transcript saved successfully', ]); - + $this->assertDatabaseHas('conversation_transcripts', [ 'session_id' => $this->session->id, 'speaker' => 'customer', @@ -217,15 +216,15 @@ ['speaker' => 'salesperson', 'text' => 'First', 'spoken_at' => now(), 'order_index' => 1], ['speaker' => 'customer', 'text' => 'Second', 'spoken_at' => now(), 'order_index' => 2], ]); - + $response = $this->postJson("/conversations/{$this->session->id}/transcript", [ 'speaker' => 'salesperson', 'text' => 'Third transcript', 'spoken_at' => now()->timestamp * 1000, ]); - + $response->assertStatus(200); - + $transcript = ConversationTranscript::where('text', 'Third transcript')->first(); expect($transcript->order_index)->toBe(3); }); @@ -236,7 +235,7 @@ 'text' => 'Test', 'spoken_at' => now()->timestamp * 1000, ]); - + $response->assertStatus(422) ->assertJsonValidationErrors(['speaker']); }); @@ -263,18 +262,18 @@ 'group_id' => 'group-2', ], ]; - + $response = $this->postJson("/conversations/{$this->session->id}/transcripts", [ 'transcripts' => $transcripts, ]); - + $response->assertStatus(200) ->assertJson([ 'message' => 'Transcripts saved successfully', ]); - + expect($this->session->transcripts()->count())->toBe(3); - + // Check order indexes $savedTranscripts = $this->session->transcripts()->orderBy('order_index')->get(); expect($savedTranscripts[0]->order_index)->toBe(1); @@ -295,11 +294,11 @@ 'spoken_at' => now()->timestamp * 1000, ], ]; - + $response = $this->postJson("/conversations/{$this->session->id}/transcripts", [ 'transcripts' => $transcripts, ]); - + $response->assertStatus(422) ->assertJsonValidationErrors(['transcripts.1.speaker']); }); @@ -314,7 +313,7 @@ ], 'captured_at' => now()->timestamp * 1000, ]); - + $response->assertStatus(200) ->assertJsonStructure([ 'insight_id', @@ -323,7 +322,7 @@ ->assertJson([ 'message' => 'Insight saved successfully', ]); - + $this->assertDatabaseHas('conversation_insights', [ 'session_id' => $this->session->id, 'insight_type' => 'key_insight', @@ -349,16 +348,16 @@ 'captured_at' => now()->addSeconds(2)->timestamp * 1000, ], ]; - + $response = $this->postJson("/conversations/{$this->session->id}/insights", [ 'insights' => $insights, ]); - + $response->assertStatus(200) ->assertJson([ 'message' => 'Insights saved successfully', ]); - + expect($this->session->insights()->count())->toBe(3); expect($this->session->insights()->where('insight_type', 'topic')->count())->toBe(1); expect($this->session->insights()->where('insight_type', 'commitment')->count())->toBe(1); @@ -370,25 +369,25 @@ $response = $this->patchJson("/conversations/{$this->session->id}/notes", [ 'user_notes' => 'Customer seems very interested in enterprise features.', ]); - + $response->assertStatus(200) ->assertJson([ 'message' => 'Notes updated successfully', ]); - + $this->session->refresh(); expect($this->session->user_notes)->toBe('Customer seems very interested in enterprise features.'); }); test('can clear session notes', function () { $this->session->update(['user_notes' => 'Some existing notes']); - + $response = $this->patchJson("/conversations/{$this->session->id}/notes", [ 'user_notes' => null, ]); - + $response->assertStatus(200); - + $this->session->refresh(); expect($this->session->user_notes)->toBeNull(); }); @@ -398,19 +397,19 @@ $response = $this->patchJson("/conversations/{$this->session->id}/title", [ 'title' => 'Important Sales Call with Acme Corp', ]); - + $response->assertStatus(200) ->assertJson([ 'message' => 'Title updated successfully', ]); - + $this->session->refresh(); expect($this->session->title)->toBe('Important Sales Call with Acme Corp'); }); test('update title validates required field', function () { $response = $this->patchJson("/conversations/{$this->session->id}/title", []); - + $response->assertStatus(422) ->assertJsonValidationErrors(['title']); }); @@ -419,7 +418,7 @@ $response = $this->patchJson("/conversations/{$this->session->id}/title", [ 'title' => str_repeat('a', 256), ]); - + $response->assertStatus(422) ->assertJsonValidationErrors(['title']); }); @@ -427,12 +426,12 @@ // Delete tests test('can delete conversation session', function () { $sessionId = $this->session->id; - + $response = $this->delete("/conversations/{$sessionId}"); - + $response->assertRedirect('/conversations') ->assertSessionHas('message', 'Conversation deleted successfully'); - + $this->assertDatabaseMissing('conversation_sessions', ['id' => $sessionId]); }); @@ -444,20 +443,20 @@ 'spoken_at' => now(), 'order_index' => 1, ]); - + $this->session->insights()->create([ 'insight_type' => 'topic', 'data' => ['text' => 'Test'], 'captured_at' => now(), ]); - + $sessionId = $this->session->id; - + $response = $this->delete("/conversations/{$sessionId}"); - + $response->assertRedirect('/conversations'); - + // Check cascade deletion $this->assertDatabaseMissing('conversation_transcripts', ['session_id' => $sessionId]); $this->assertDatabaseMissing('conversation_insights', ['session_id' => $sessionId]); -}); \ No newline at end of file +}); diff --git a/tests/Feature/Controllers/RealtimeControllerTest.php b/tests/Feature/Controllers/RealtimeControllerTest.php index 21ff5ab..7c94ae4 100644 --- a/tests/Feature/Controllers/RealtimeControllerTest.php +++ b/tests/Feature/Controllers/RealtimeControllerTest.php @@ -1,6 +1,5 @@ mockEphemeralKeySuccess(); - + // Act $response = $this->postJson('/api/realtime/ephemeral-key'); - + // Assert $response->assertStatus(200) ->assertJsonStructure([ @@ -31,17 +30,17 @@ 'status' => 'success', 'model' => 'gpt-4o-mini-realtime-preview-2024-12-17', ]); - + expect($response->json('ephemeralKey'))->toStartWith('ek_'); }); test('generateEphemeralKey returns error when no API key configured', function () { // Arrange Config::set('openai.api_key', null); // Ensure no config key - + // Act $response = $this->postJson('/api/realtime/ephemeral-key'); - + // Assert $response->assertStatus(422) ->assertJson([ @@ -54,10 +53,10 @@ // Arrange Cache::put('app_openai_api_key', mockApiKey()); $this->mockEphemeralKeyFailure(); - + // Act $response = $this->postJson('/api/realtime/ephemeral-key'); - + // Assert $response->assertStatus(500) ->assertJson([ @@ -70,10 +69,10 @@ // Arrange Cache::put('app_openai_api_key', mockApiKey()); $this->mockEphemeralKeyInvalidResponse(); - + // Act $response = $this->postJson('/api/realtime/ephemeral-key'); - + // Assert $response->assertStatus(500) ->assertJson([ @@ -88,15 +87,16 @@ Http::fake([ 'api.openai.com/v1/realtime/sessions' => function ($request) { expect($request->data()['voice'])->toBe('nova'); + return Http::response(mockEphemeralKeyResponse()); }, ]); - + // Act $response = $this->postJson('/api/realtime/ephemeral-key', [ 'voice' => 'nova', ]); - + // Assert $response->assertStatus(200); }); @@ -107,13 +107,14 @@ Http::fake([ 'api.openai.com/v1/realtime/sessions' => function ($request) { expect($request->data()['voice'])->toBe('alloy'); + return Http::response(mockEphemeralKeyResponse()); }, ]); - + // Act $response = $this->postJson('/api/realtime/ephemeral-key'); - + // Assert $response->assertStatus(200); }); @@ -122,10 +123,10 @@ // Arrange Cache::put('app_openai_api_key', mockApiKey()); $this->mockHttpTimeout(); - + // Act $response = $this->postJson('/api/realtime/ephemeral-key'); - + // Assert $response->assertStatus(500) ->assertJson([ @@ -138,11 +139,11 @@ // Arrange Config::set('openai.api_key', mockApiKey()); $this->mockEphemeralKeySuccess(); - + // Act $response = $this->postJson('/api/realtime/ephemeral-key'); - + // Assert $response->assertStatus(200) ->assertJson(['status' => 'success']); -}); \ No newline at end of file +}); diff --git a/tests/Feature/Controllers/Settings/ApiKeyControllerTest.php b/tests/Feature/Controllers/Settings/ApiKeyControllerTest.php index 6773764..c201dce 100644 --- a/tests/Feature/Controllers/Settings/ApiKeyControllerTest.php +++ b/tests/Feature/Controllers/Settings/ApiKeyControllerTest.php @@ -11,7 +11,7 @@ test('api keys settings page can be viewed', function () { $response = $this->get('/settings/api-keys'); - + $response->assertStatus(200) ->assertInertia(fn ($page) => $page ->component('settings/ApiKeys') @@ -21,9 +21,9 @@ test('api keys page shows when key exists in cache', function () { Cache::put('app_openai_api_key', mockApiKey()); - + $response = $this->get('/settings/api-keys'); - + $response->assertStatus(200) ->assertInertia(fn ($page) => $page ->where('hasApiKey', true) @@ -33,9 +33,9 @@ test('api keys page shows when using environment key', function () { Config::set('openai.api_key', mockApiKey()); - + $response = $this->get('/settings/api-keys'); - + $response->assertStatus(200) ->assertInertia(fn ($page) => $page ->where('hasApiKey', true) @@ -46,24 +46,24 @@ test('can update api key with valid key', function () { $apiKey = mockApiKey(); $this->mockOpenAIModelsSuccess(); - + $response = $this->put('/settings/api-keys', [ 'openai_api_key' => $apiKey, ]); - + $response->assertRedirect('/settings/api-keys') ->assertSessionHas('success', 'API key updated successfully.'); - + expect(Cache::get('app_openai_api_key'))->toBe($apiKey); }); test('cannot update api key with invalid key', function () { $this->mockOpenAIModelsFailure(); - + $response = $this->put('/settings/api-keys', [ 'openai_api_key' => 'invalid-key', ]); - + $response->assertSessionHasErrors(['openai_api_key']); expect(Cache::has('app_openai_api_key'))->toBeFalse(); }); @@ -72,61 +72,62 @@ $response = $this->put('/settings/api-keys', [ 'openai_api_key' => 'short', ]); - + $response->assertSessionHasErrors(['openai_api_key']); }); test('cannot update api key without providing key', function () { $response = $this->put('/settings/api-keys', []); - + $response->assertSessionHasErrors(['openai_api_key']); }); test('can delete api key', function () { Cache::put('app_openai_api_key', mockApiKey()); - + $response = $this->delete('/settings/api-keys'); - + $response->assertRedirect('/settings/api-keys') ->assertSessionHas('success', 'API key deleted successfully.'); - + expect(Cache::has('app_openai_api_key'))->toBeFalse(); }); test('deleting non-existent api key still succeeds', function () { $response = $this->delete('/settings/api-keys'); - + $response->assertRedirect('/settings/api-keys') ->assertSessionHas('success', 'API key deleted successfully.'); }); test('api key validation handles connection errors gracefully', function () { $this->mockHttpTimeout(); - + $response = $this->put('/settings/api-keys', [ 'openai_api_key' => mockApiKey(), ]); - + $response->assertSessionHasErrors(['openai_api_key']); expect(Cache::has('app_openai_api_key'))->toBeFalse(); }); test('api key is properly validated with OpenAI before saving', function () { $apiKey = mockApiKey(); - + Http::fake([ 'api.openai.com/v1/models' => Http::response(function ($request) use ($apiKey) { expect($request->header('Authorization')[0])->toBe("Bearer {$apiKey}"); + return [ 'object' => 'list', 'data' => [['id' => 'gpt-4', 'object' => 'model']], ]; }, 200), ]); - + $response = $this->put('/settings/api-keys', [ 'openai_api_key' => $apiKey, ]); - + $response->assertRedirect('/settings/api-keys'); -}); \ No newline at end of file +}); diff --git a/tests/Feature/DashboardTest.php b/tests/Feature/DashboardTest.php index e62dcca..4b18a89 100644 --- a/tests/Feature/DashboardTest.php +++ b/tests/Feature/DashboardTest.php @@ -7,7 +7,7 @@ $mockApiKeyService = Mockery::mock(ApiKeyService::class); $mockApiKeyService->shouldReceive('hasApiKey')->andReturn(true); $this->app->instance(ApiKeyService::class, $mockApiKeyService); - + $response = $this->get('/dashboard'); $response->assertStatus(200); }); @@ -17,7 +17,7 @@ $mockApiKeyService = Mockery::mock(ApiKeyService::class); $mockApiKeyService->shouldReceive('hasApiKey')->andReturn(true); $this->app->instance(ApiKeyService::class, $mockApiKeyService); - + $response = $this->get('/realtime-agent'); $response->assertStatus(200); }); diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php index 4e79405..5262fb1 100644 --- a/tests/Feature/ExampleTest.php +++ b/tests/Feature/ExampleTest.php @@ -7,7 +7,7 @@ $mockApiKeyService = Mockery::mock(ApiKeyService::class); $mockApiKeyService->shouldReceive('hasApiKey')->andReturn(true); $this->app->instance(ApiKeyService::class, $mockApiKeyService); - + $response = $this->get('/'); $response->assertStatus(200); diff --git a/tests/Feature/OpenExternalTest.php b/tests/Feature/OpenExternalTest.php index 5bd076a..1f2388b 100644 --- a/tests/Feature/OpenExternalTest.php +++ b/tests/Feature/OpenExternalTest.php @@ -14,7 +14,7 @@ class OpenExternalTest extends TestCase protected function setUp(): void { parent::setUp(); - + // Reset Shell facade mock for each test Shell::clearResolvedInstance('Shell'); } @@ -32,13 +32,13 @@ public function test_opens_valid_url_successfully(): void ->with('https://github.com/vijaythecoder/clueless'); $response = $this->postJson('/api/open-external', [ - 'url' => 'https://github.com/vijaythecoder/clueless' + 'url' => 'https://github.com/vijaythecoder/clueless', ]); $response->assertStatus(200) - ->assertJson([ - 'success' => true - ]); + ->assertJson([ + 'success' => true, + ]); } public function test_opens_openai_url_successfully(): void @@ -48,25 +48,25 @@ public function test_opens_openai_url_successfully(): void ->with('https://platform.openai.com/api-keys'); $response = $this->postJson('/api/open-external', [ - 'url' => 'https://platform.openai.com/api-keys' + 'url' => 'https://platform.openai.com/api-keys', ]); $response->assertStatus(200) - ->assertJson([ - 'success' => true - ]); + ->assertJson([ + 'success' => true, + ]); } public function test_rejects_invalid_url(): void { $response = $this->postJson('/api/open-external', [ - 'url' => 'not-a-valid-url' + 'url' => 'not-a-valid-url', ]); $response->assertStatus(400) - ->assertJson([ - 'error' => 'Invalid URL' - ]); + ->assertJson([ + 'error' => 'Invalid URL', + ]); } public function test_rejects_malicious_urls(): void @@ -74,18 +74,18 @@ public function test_rejects_malicious_urls(): void $maliciousUrls = [ 'javascript:alert("xss")', 'data:text/html,', - 'not-a-url' + 'not-a-url', ]; foreach ($maliciousUrls as $url) { $response = $this->postJson('/api/open-external', [ - 'url' => $url + 'url' => $url, ]); $response->assertStatus(400) - ->assertJson([ - 'error' => 'Invalid URL' - ]); + ->assertJson([ + 'error' => 'Invalid URL', + ]); } } @@ -99,25 +99,25 @@ public function test_requires_url_parameter(): void public function test_handles_empty_url(): void { $response = $this->postJson('/api/open-external', [ - 'url' => '' + 'url' => '', ]); $response->assertStatus(400) - ->assertJson([ - 'error' => 'Invalid URL' - ]); + ->assertJson([ + 'error' => 'Invalid URL', + ]); } public function test_handles_null_url(): void { $response = $this->postJson('/api/open-external', [ - 'url' => null + 'url' => null, ]); $response->assertStatus(400) - ->assertJson([ - 'error' => 'Invalid URL' - ]); + ->assertJson([ + 'error' => 'Invalid URL', + ]); } public function test_allows_https_urls(): void @@ -126,7 +126,7 @@ public function test_allows_https_urls(): void 'https://github.com', 'https://platform.openai.com', 'https://www.example.com', - 'https://subdomain.example.com/path?query=value' + 'https://subdomain.example.com/path?query=value', ]; foreach ($validUrls as $url) { @@ -135,13 +135,13 @@ public function test_allows_https_urls(): void ->with($url); $response = $this->postJson('/api/open-external', [ - 'url' => $url + 'url' => $url, ]); $response->assertStatus(200) - ->assertJson([ - 'success' => true - ]); + ->assertJson([ + 'success' => true, + ]); } } @@ -152,13 +152,13 @@ public function test_allows_http_urls(): void ->with('http://example.com'); $response = $this->postJson('/api/open-external', [ - 'url' => 'http://example.com' + 'url' => 'http://example.com', ]); $response->assertStatus(200) - ->assertJson([ - 'success' => true - ]); + ->assertJson([ + 'success' => true, + ]); } public function test_handles_shell_exception(): void @@ -169,11 +169,11 @@ public function test_handles_shell_exception(): void ->andThrow(new \Exception('Shell error')); $response = $this->postJson('/api/open-external', [ - 'url' => 'https://github.com' + 'url' => 'https://github.com', ]); // The route doesn't handle exceptions explicitly, so it would return 500 // In a real implementation, you might want to catch and handle this $response->assertStatus(500); } -} \ No newline at end of file +} diff --git a/tests/Pest.php b/tests/Pest.php index 4ee2362..32eed23 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -49,7 +49,7 @@ */ function mockApiKey(): string { - return 'sk-test-' . str_repeat('x', 40); + return 'sk-test-'.str_repeat('x', 40); } /** @@ -58,11 +58,11 @@ function mockApiKey(): string function mockEphemeralKeyResponse(): array { return [ - 'id' => 'sess_' . uniqid(), + 'id' => 'sess_'.uniqid(), 'object' => 'realtime.session', 'model' => 'gpt-4o-mini-realtime-preview-2024-12-17', 'client_secret' => [ - 'value' => 'ek_' . bin2hex(random_bytes(32)), + 'value' => 'ek_'.bin2hex(random_bytes(32)), 'expires_at' => time() + 7200, // 2 hours from now ], 'tools' => [], diff --git a/tests/Traits/MocksOpenAI.php b/tests/Traits/MocksOpenAI.php index aa60e4e..185ee9e 100644 --- a/tests/Traits/MocksOpenAI.php +++ b/tests/Traits/MocksOpenAI.php @@ -87,4 +87,4 @@ protected function mockHttpTimeout(): void throw new \Illuminate\Http\Client\ConnectionException('Connection timed out'); }); } -} \ No newline at end of file +} diff --git a/tests/Unit/Middleware/CheckOnboardingTest.php b/tests/Unit/Middleware/CheckOnboardingTest.php deleted file mode 100644 index 6663dd5..0000000 --- a/tests/Unit/Middleware/CheckOnboardingTest.php +++ /dev/null @@ -1,144 +0,0 @@ -mockApiKeyService = Mockery::mock(ApiKeyService::class); - $this->middleware = new CheckOnboarding($this->mockApiKeyService); - } - - protected function tearDown(): void - { - Mockery::close(); - parent::tearDown(); - } - - public function test_allows_onboarding_route_without_api_key(): void - { - $request = Request::create('/onboarding', 'GET'); - $route = new Route(['GET'], '/onboarding', []); - $route->name('onboarding'); - $request->setRouteResolver(fn() => $route); - - $this->mockApiKeyService->shouldNotReceive('hasApiKey'); - - $response = $this->middleware->handle($request, function ($req) { - return new Response('OK'); - }); - - $this->assertEquals('OK', $response->getContent()); - } - - public function test_allows_api_key_settings_routes_without_api_key(): void - { - $excludedRoutes = [ - 'api-keys.edit', - 'api-keys.update', - 'api-keys.destroy', - 'api.openai.status', - 'api.openai.api-key.store', - 'appearance' - ]; - - foreach ($excludedRoutes as $routeName) { - $request = Request::create('/test', 'GET'); - $route = new Route(['GET'], '/test', []); - $route->name($routeName); - $request->setRouteResolver(fn() => $route); - - $this->mockApiKeyService->shouldNotReceive('hasApiKey'); - - $response = $this->middleware->handle($request, function ($req) { - return new Response('OK'); - }); - - $this->assertEquals('OK', $response->getContent(), "Route {$routeName} should be excluded"); - } - } - - public function test_allows_api_routes_without_api_key(): void - { - $request = Request::create('/api/some-endpoint', 'POST'); - $route = new Route(['POST'], '/api/some-endpoint', []); - $route->name('api.some-endpoint'); - $request->setRouteResolver(fn() => $route); - - $this->mockApiKeyService->shouldNotReceive('hasApiKey'); - - $response = $this->middleware->handle($request, function ($req) { - return new Response('OK'); - }); - - $this->assertEquals('OK', $response->getContent()); - } - - public function test_redirects_to_onboarding_when_no_api_key(): void - { - $request = Request::create('/dashboard', 'GET'); - $route = new Route(['GET'], '/dashboard', []); - $route->name('dashboard'); - $request->setRouteResolver(fn() => $route); - - $this->mockApiKeyService->shouldReceive('hasApiKey') - ->once() - ->andReturn(false); - - $response = $this->middleware->handle($request, function ($req) { - return new Response('Should not reach here'); - }); - - $this->assertEquals(302, $response->getStatusCode()); - $this->assertTrue(str_contains($response->headers->get('Location'), '/onboarding')); - } - - public function test_allows_access_when_api_key_exists(): void - { - $request = Request::create('/dashboard', 'GET'); - $route = new Route(['GET'], '/dashboard', []); - $route->name('dashboard'); - $request->setRouteResolver(fn() => $route); - - $this->mockApiKeyService->shouldReceive('hasApiKey') - ->once() - ->andReturn(true); - - $response = $this->middleware->handle($request, function ($req) { - return new Response('Dashboard content'); - }); - - $this->assertEquals('Dashboard content', $response->getContent()); - } - - public function test_handles_request_without_route(): void - { - $request = Request::create('/some-path', 'GET'); - // No route set - route() returns null - - $this->mockApiKeyService->shouldReceive('hasApiKey') - ->once() - ->andReturn(false); - - $response = $this->middleware->handle($request, function ($req) { - return new Response('OK'); - }); - - // Without a route, middleware allows the request to continue - $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals('OK', $response->getContent()); - } -} \ No newline at end of file diff --git a/tests/Unit/Services/ApiKeyServiceTest.php b/tests/Unit/Services/ApiKeyServiceTest.php index a416ff3..c57739c 100644 --- a/tests/Unit/Services/ApiKeyServiceTest.php +++ b/tests/Unit/Services/ApiKeyServiceTest.php @@ -8,118 +8,118 @@ uses(MocksOpenAI::class); beforeEach(function () { - $this->service = new ApiKeyService(); + $this->service = new ApiKeyService; Cache::flush(); // Clear cache before each test }); test('getApiKey returns cached key when available', function () { $cachedKey = mockApiKey(); Cache::put('app_openai_api_key', $cachedKey); - + $result = $this->service->getApiKey(); - + expect($result)->toBe($cachedKey); }); test('getApiKey falls back to config when no cached key', function () { $configKey = 'sk-config-key'; Config::set('openai.api_key', $configKey); - + $result = $this->service->getApiKey(); - + expect($result)->toBe($configKey); }); test('getApiKey returns null when no key available', function () { Config::set('openai.api_key', null); - + $result = $this->service->getApiKey(); - + expect($result)->toBeNull(); }); test('setApiKey stores key in cache permanently', function () { $apiKey = mockApiKey(); - + $this->service->setApiKey($apiKey); - + expect(Cache::get('app_openai_api_key'))->toBe($apiKey); }); test('removeApiKey removes key from cache', function () { $apiKey = mockApiKey(); Cache::put('app_openai_api_key', $apiKey); - + $this->service->removeApiKey(); - + expect(Cache::has('app_openai_api_key'))->toBeFalse(); }); test('hasApiKey returns true when cached key exists', function () { Cache::put('app_openai_api_key', mockApiKey()); - + $result = $this->service->hasApiKey(); - + expect($result)->toBeTrue(); }); test('hasApiKey returns true when config key exists', function () { Config::set('openai.api_key', 'sk-config-key'); - + $result = $this->service->hasApiKey(); - + expect($result)->toBeTrue(); }); test('hasApiKey returns false when no key exists', function () { Config::set('openai.api_key', null); - + $result = $this->service->hasApiKey(); - + expect($result)->toBeFalse(); }); test('hasApiKey returns false for empty string key', function () { Cache::put('app_openai_api_key', ''); Config::set('openai.api_key', null); // Ensure no fallback - + $result = $this->service->hasApiKey(); - + expect($result)->toBeFalse(); }); test('validateApiKey returns true for valid key', function () { $this->mockOpenAIModelsSuccess(); - + $result = $this->service->validateApiKey(mockApiKey()); - + expect($result)->toBeTrue(); }); test('validateApiKey returns false for invalid key', function () { $this->mockOpenAIModelsFailure(); - + $result = $this->service->validateApiKey('invalid-key'); - + expect($result)->toBeFalse(); }); test('validateApiKey returns false on connection error', function () { $this->mockHttpTimeout(); - + $result = $this->service->validateApiKey(mockApiKey()); - + expect($result)->toBeFalse(); }); test('priority is cached key over config key', function () { $cachedKey = 'sk-cached-key'; $configKey = 'sk-config-key'; - + Cache::put('app_openai_api_key', $cachedKey); Config::set('openai.api_key', $configKey); - + $result = $this->service->getApiKey(); - + expect($result)->toBe($cachedKey); -}); \ No newline at end of file +}); diff --git a/tests/Unit/Services/VariableServiceTest.php b/tests/Unit/Services/VariableServiceTest.php index e95f255..49616cd 100644 --- a/tests/Unit/Services/VariableServiceTest.php +++ b/tests/Unit/Services/VariableServiceTest.php @@ -5,13 +5,12 @@ use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Config; -use Illuminate\Support\Facades\DB; use Illuminate\Validation\ValidationException; uses(RefreshDatabase::class); beforeEach(function () { - $this->service = new VariableService(); + $this->service = new VariableService; Cache::flush(); // Clear cache before each test }); @@ -30,16 +29,16 @@ 'type' => 'number', 'category' => 'test', ]); - + // First call should query database $result = $this->service->getAll(); - + expect($result)->toHaveCount(2); expect($result->first()->key)->toBe('test_var_1'); - + // Delete one variable directly (bypassing service to test cache) Variable::where('key', 'test_var_1')->delete(); - + // Should still return cached result $cachedResult = $this->service->getAll(); expect($cachedResult)->toHaveCount(2); @@ -49,9 +48,9 @@ Variable::create(['key' => 'z_var', 'value' => 'val', 'type' => 'text', 'category' => 'beta']); Variable::create(['key' => 'a_var', 'value' => 'val', 'type' => 'text', 'category' => 'beta']); Variable::create(['key' => 'b_var', 'value' => 'val', 'type' => 'text', 'category' => 'alpha']); - + $result = $this->service->getAll(); - + expect($result[0]->category)->toBe('alpha'); expect($result[1]->key)->toBe('a_var'); expect($result[2]->key)->toBe('z_var'); @@ -62,16 +61,16 @@ Variable::create(['key' => 'prod_1', 'value' => 'val', 'type' => 'text', 'category' => 'product']); Variable::create(['key' => 'prod_2', 'value' => 'val', 'type' => 'text', 'category' => 'product']); Variable::create(['key' => 'price_1', 'value' => '100', 'type' => 'number', 'category' => 'pricing']); - + $result = $this->service->getByCategory('product'); - + expect($result)->toHaveCount(2); expect($result->pluck('key')->toArray())->toBe(['prod_1', 'prod_2']); }); test('getByCategory returns empty collection for non-existent category', function () { $result = $this->service->getByCategory('non_existent'); - + expect($result)->toBeEmpty(); }); @@ -83,9 +82,9 @@ 'type' => 'text', 'category' => 'test', ]); - + $result = $this->service->getByKey('test_key'); - + expect($result)->not->toBeNull(); expect($result->id)->toBe($variable->id); expect($result->value)->toBe('test_value'); @@ -93,7 +92,7 @@ test('getByKey returns null for non-existent key', function () { $result = $this->service->getByKey('non_existent'); - + expect($result)->toBeNull(); }); @@ -107,13 +106,13 @@ 'category' => 'test', 'is_system' => false, ]; - + $result = $this->service->upsert($data); - + expect($result)->toBeInstanceOf(Variable::class); expect($result->key)->toBe('new_var'); expect($result->value)->toBe('new_value'); - + $this->assertDatabaseHas('variables', [ 'key' => 'new_var', 'value' => 'new_value', @@ -127,16 +126,16 @@ 'type' => 'text', 'category' => 'test', ]); - + $data = [ 'key' => 'existing_var', 'value' => 'new_value', 'type' => 'text', 'category' => 'updated', ]; - + $result = $this->service->upsert($data); - + expect($result->id)->toBe($variable->id); expect($result->value)->toBe('new_value'); expect($result->category)->toBe('updated'); @@ -147,8 +146,8 @@ 'key' => 'test_var', // Missing required fields ]; - - expect(fn() => $this->service->upsert($data)) + + expect(fn () => $this->service->upsert($data)) ->toThrow(ValidationException::class); }); @@ -159,8 +158,8 @@ 'type' => 'invalid_type', 'category' => 'test', ]; - - expect(fn() => $this->service->upsert($data)) + + expect(fn () => $this->service->upsert($data)) ->toThrow(ValidationException::class); }); @@ -171,11 +170,11 @@ 'type' => 'text', 'category' => 'test', ]); - + // Populate cache $this->service->getByKey('cached_var'); $this->service->getByCategory('test'); - + // Update variable $this->service->upsert([ 'key' => 'cached_var', @@ -183,7 +182,7 @@ 'type' => 'text', 'category' => 'test', ]); - + // Verify cache was cleared by checking new value is returned $result = $this->service->getByKey('cached_var'); expect($result->value)->toBe('new'); @@ -198,16 +197,16 @@ 'category' => 'test', 'is_system' => false, ]); - + $result = $this->service->delete('to_delete'); - + expect($result)->toBeTrue(); $this->assertDatabaseMissing('variables', ['key' => 'to_delete']); }); test('delete returns false for non-existent variable', function () { $result = $this->service->delete('non_existent'); - + expect($result)->toBeFalse(); }); @@ -219,8 +218,8 @@ 'category' => 'test', 'is_system' => true, ]); - - expect(fn() => $this->service->delete('system_var')) + + expect(fn () => $this->service->delete('system_var')) ->toThrow(\Exception::class, 'Cannot delete system variables'); }); @@ -232,12 +231,12 @@ 'category' => 'test', 'is_system' => false, ]); - + // Populate cache $this->service->getByKey('cached_delete'); - + $this->service->delete('cached_delete'); - + // Verify variable is gone $result = $this->service->getByKey('cached_delete'); expect($result)->toBeNull(); @@ -247,20 +246,20 @@ test('replaceInText replaces variable placeholders', function () { Variable::create(['key' => 'product_name', 'value' => 'Awesome App', 'type' => 'text', 'category' => 'product']); Variable::create(['key' => 'price', 'value' => '99', 'type' => 'number', 'category' => 'pricing']); - + $text = 'Welcome to {product_name}! It costs ${price} per month.'; $result = $this->service->replaceInText($text); - + expect($result)->toBe('Welcome to Awesome App! It costs $99 per month.'); }); test('replaceInText handles boolean values', function () { Variable::create(['key' => 'is_active', 'value' => 'true', 'type' => 'boolean', 'category' => 'settings']); Variable::create(['key' => 'is_beta', 'value' => 'false', 'type' => 'boolean', 'category' => 'settings']); - + $text = 'Active: {is_active}, Beta: {is_beta}'; $result = $this->service->replaceInText($text); - + expect($result)->toBe('Active: true, Beta: false'); }); @@ -271,26 +270,26 @@ 'type' => 'json', 'category' => 'product', ]); - + $text = 'Features: {features}'; $result = $this->service->replaceInText($text); - + expect($result)->toBe('Features: ["feature1","feature2"]'); }); test('replaceInText uses overrides', function () { Variable::create(['key' => 'name', 'value' => 'Default Name', 'type' => 'text', 'category' => 'test']); - + $text = 'Hello {name}!'; $result = $this->service->replaceInText($text, ['name' => 'Override Name']); - + expect($result)->toBe('Hello Override Name!'); }); test('replaceInText leaves unmatched placeholders', function () { $text = 'This {non_existent} variable does not exist'; $result = $this->service->replaceInText($text); - + expect($result)->toBe('This {non_existent} variable does not exist'); }); @@ -303,9 +302,9 @@ 'category' => 'test', 'validation_rules' => ['email'], ]); - + $result = $this->service->validate('email_var', 'valid@email.com'); - + expect($result)->toBeTrue(); }); @@ -317,15 +316,15 @@ 'category' => 'test', 'validation_rules' => ['email'], ]); - + $result = $this->service->validate('email_var', 'invalid-email'); - + expect($result)->toBeFalse(); }); test('validate returns false for non-existent variable', function () { $result = $this->service->validate('non_existent', 'any_value'); - + expect($result)->toBeFalse(); }); @@ -337,9 +336,9 @@ 'category' => 'test', 'validation_rules' => null, ]); - + $result = $this->service->validate('no_rules', 'any_value'); - + expect($result)->toBeTrue(); }); @@ -360,14 +359,14 @@ 'category' => 'test', 'is_system' => false, ]); - + $result = $this->service->export(); - + expect($result)->toHaveKeys(['version', 'exported_at', 'variables']); expect($result['version'])->toBe('1.0'); expect($result['variables'])->toHaveCount(2); expect($result['variables'][0])->toHaveKeys([ - 'key', 'value', 'type', 'description', 'category', 'is_system', 'validation_rules' + 'key', 'value', 'type', 'description', 'category', 'is_system', 'validation_rules', ]); }); @@ -390,9 +389,9 @@ ], ], ]; - + $this->service->import($data); - + $this->assertDatabaseHas('variables', ['key' => 'imported1']); $this->assertDatabaseHas('variables', ['key' => 'imported2']); }); @@ -404,7 +403,7 @@ 'type' => 'text', 'category' => 'test', ]); - + $data = [ 'version' => '1.0', 'variables' => [ @@ -416,9 +415,9 @@ ], ], ]; - + $this->service->import($data); - + $variable = Variable::where('key', 'existing')->first(); expect($variable->value)->toBe('new'); expect($variable->category)->toBe('updated'); @@ -437,15 +436,15 @@ ], ], ]; - + $this->service->import($data); - + $this->assertDatabaseMissing('variables', ['key' => 'system_import']); }); test('import allows system variables when configured', function () { Config::set('app.allow_system_variable_import', true); - + $data = [ 'version' => '1.0', 'variables' => [ @@ -458,9 +457,9 @@ ], ], ]; - + $this->service->import($data); - + $this->assertDatabaseHas('variables', ['key' => 'system_import']); }); @@ -471,8 +470,8 @@ ['key' => 'test'], ], ]; - - expect(fn() => $this->service->import($data)) + + expect(fn () => $this->service->import($data)) ->toThrow(ValidationException::class); }); @@ -494,13 +493,13 @@ ], ], ]; - + try { $this->service->import($data); } catch (\Exception $e) { // Expected to fail } - + // Neither variable should be created due to transaction rollback $this->assertDatabaseMissing('variables', ['key' => 'valid']); $this->assertDatabaseMissing('variables', ['key' => 'invalid']); @@ -512,9 +511,9 @@ Variable::create(['key' => 'num_var', 'value' => '42', 'type' => 'number', 'category' => 'test']); Variable::create(['key' => 'bool_var', 'value' => 'true', 'type' => 'boolean', 'category' => 'test']); Variable::create(['key' => 'json_var', 'value' => '{"a":1}', 'type' => 'json', 'category' => 'test']); - + $result = $this->service->getVariablesAsArray(); - + expect($result)->toBe([ 'text_var' => 'text', 'num_var' => 42.0, @@ -529,21 +528,21 @@ Variable::create(['key' => 'v2', 'value' => 'val', 'type' => 'text', 'category' => 'alpha']); Variable::create(['key' => 'v3', 'value' => 'val', 'type' => 'text', 'category' => 'alpha']); // Duplicate Variable::create(['key' => 'v4', 'value' => 'val', 'type' => 'text', 'category' => 'beta']); - + $result = $this->service->getCategories(); - + expect($result->toArray())->toBe(['alpha', 'beta', 'zebra']); }); // Seed defaults tests test('seedDefaults creates default variables', function () { $this->service->seedDefaults(); - + $this->assertDatabaseHas('variables', ['key' => 'product_name']); $this->assertDatabaseHas('variables', ['key' => 'company_name']); $this->assertDatabaseHas('variables', ['key' => 'pricing_starter']); $this->assertDatabaseHas('variables', ['key' => 'support_email']); - + $supportEmail = Variable::where('key', 'support_email')->first(); expect($supportEmail->validation_rules)->toBe(['email']); expect($supportEmail->is_system)->toBeTrue(); @@ -556,9 +555,9 @@ 'type' => 'text', 'category' => 'product', ]); - + $this->service->seedDefaults(); - + $product = Variable::where('key', 'product_name')->first(); expect($product->value)->toBe('Existing Product'); -}); \ No newline at end of file +});