From 4edfaf49061c9bed6f214465dae8bcc441a63bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=93=D0=BB=D0=B0=D0=B7=D1=83?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Thu, 15 Jan 2026 18:50:55 +0300 Subject: [PATCH] Implement logging functionality and enhance audio capture error handling --- entitlements.plist | 2 + forge.config.js | 41 ++++++++++---- package.json | 1 + pnpm-lock.yaml | 17 ++++++ src/components/views/HelpView.js | 43 ++++++++++++++ src/index.js | 22 ++++++++ src/utils/gemini.js | 65 +++++++++++++++++++-- src/utils/logger.js | 97 ++++++++++++++++++++++++++++++++ src/utils/openai-sdk.js | 71 ++++++++++++++++++++--- 9 files changed, 336 insertions(+), 23 deletions(-) create mode 100644 src/utils/logger.js diff --git a/entitlements.plist b/entitlements.plist index 61b197a..8aad2a6 100644 --- a/entitlements.plist +++ b/entitlements.plist @@ -10,6 +10,8 @@ com.apple.security.cs.disable-library-validation + com.apple.security.cs.allow-dyld-environment-variables + com.apple.security.device.audio-input com.apple.security.device.microphone diff --git a/forge.config.js b/forge.config.js index f76082f..4582a81 100644 --- a/forge.config.js +++ b/forge.config.js @@ -1,5 +1,7 @@ const { FusesPlugin } = require('@electron-forge/plugin-fuses'); const { FuseV1Options, FuseVersion } = require('@electron/fuses'); +const path = require('path'); +const fs = require('fs'); module.exports = { packagerConfig: { @@ -7,18 +9,37 @@ module.exports = { extraResource: ['./src/assets/SystemAudioDump'], name: 'Cheating Daddy', icon: 'src/assets/logo', + // Fix executable permissions after packaging + afterCopy: [ + (buildPath, electronVersion, platform, arch, callback) => { + if (platform === 'darwin') { + const systemAudioDump = path.join(buildPath, '..', 'Resources', 'SystemAudioDump'); + if (fs.existsSync(systemAudioDump)) { + try { + fs.chmodSync(systemAudioDump, 0o755); + console.log('✓ Set executable permissions for SystemAudioDump'); + } catch (err) { + console.error('✗ Failed to set permissions:', err.message); + } + } else { + console.warn('SystemAudioDump not found at:', systemAudioDump); + } + } + callback(); + }, + ], // use `security find-identity -v -p codesigning` to find your identity // for macos signing - // also fuck apple - // osxSign: { - // identity: '', - // optionsForFile: (filePath) => { - // return { - // entitlements: 'entitlements.plist', - // }; - // }, - // }, - // notarize if off cuz i ran this for 6 hours and it still didnt finish + // Use ad-hoc signing with entitlements for local development + osxSign: { + identity: '-', // ad-hoc signing (no Apple Developer account needed) + optionsForFile: (filePath) => { + return { + entitlements: 'entitlements.plist', + }; + }, + }, + // notarize is off - requires Apple Developer account // osxNotarize: { // appleId: 'your apple id', // appleIdPassword: 'app specific password', diff --git a/package.json b/package.json index 62920af..3e66720 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@electron-forge/plugin-auto-unpack-natives": "^7.11.1", "@electron-forge/plugin-fuses": "^7.11.1", "@electron/fuses": "^2.0.0", + "@electron/osx-sign": "^2.3.0", "@reforged/maker-appimage": "^5.1.1", "electron": "^39.2.7" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 90f36d4..ba55f6a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,6 +48,9 @@ importers: '@electron/fuses': specifier: ^2.0.0 version: 2.0.0 + '@electron/osx-sign': + specifier: ^2.3.0 + version: 2.3.0 '@reforged/maker-appimage': specifier: ^5.1.1 version: 5.1.1 @@ -173,6 +176,11 @@ packages: engines: {node: '>=12.0.0'} hasBin: true + '@electron/osx-sign@2.3.0': + resolution: {integrity: sha512-qh/BkWUHITP2W1grtCWn8Pm4EML3q1Rfo2tnd/KHo+f1le5gAYotBc6yEfK6X2VHyN21mPZ3CjvOiYOwjimBlA==} + engines: {node: '>=22.12.0'} + hasBin: true + '@electron/packager@18.4.4': resolution: {integrity: sha512-fTUCmgL25WXTcFpM1M72VmFP8w3E4d+KNzWxmTDRpvwkfn/S206MAtM2cy0GF78KS9AwASMOUmlOIzCHeNxcGQ==} engines: {node: '>= 16.13.0'} @@ -2487,6 +2495,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@electron/osx-sign@2.3.0': + dependencies: + debug: 4.4.3 + isbinaryfile: 4.0.10 + plist: 3.1.0 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + '@electron/packager@18.4.4': dependencies: '@electron/asar': 3.4.1 diff --git a/src/components/views/HelpView.js b/src/components/views/HelpView.js index 7f6f44b..5ecd6a8 100644 --- a/src/components/views/HelpView.js +++ b/src/components/views/HelpView.js @@ -170,6 +170,25 @@ export class HelpView extends LitElement { flex-shrink: 0; } + .open-logs-btn { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 14px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 4px; + color: var(--text-color); + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: background 0.15s ease; + } + + .open-logs-btn:hover { + background: var(--hover-background); + } + .usage-steps { counter-reset: step-counter; } @@ -442,9 +461,33 @@ export class HelpView extends LitElement {
The AI listens to conversations and provides contextual assistance based on what it hears.
+ +
+
+ Troubleshooting +
+
+ If you're experiencing issues with audio capture or other features, check the application logs for diagnostic information. +
+ +
`; } + + async openLogsFolder() { + try { + const { ipcRenderer } = require('electron'); + const result = await ipcRenderer.invoke('open-logs-folder'); + if (!result.success) { + console.error('Failed to open logs folder:', result.error); + } + } catch (err) { + console.error('Error opening logs folder:', err); + } + } } customElements.define('help-view', HelpView); diff --git a/src/index.js b/src/index.js index fbf3152..89aa83f 100644 --- a/src/index.js +++ b/src/index.js @@ -6,6 +6,7 @@ const { app, BrowserWindow, shell, ipcMain } = require('electron'); const { createWindow, updateGlobalShortcuts } = require('./utils/window'); const { setupAIProviderIpcHandlers } = require('./utils/ai-provider-manager'); const { stopMacOSAudioCapture } = require('./utils/gemini'); +const { initLogger, closeLogger, getLogPath } = require('./utils/logger'); const storage = require('./storage'); const geminiSessionRef = { current: null }; @@ -24,6 +25,10 @@ function createMainWindow() { } app.whenReady().then(async () => { + // Initialize file logger first + const logPath = initLogger(); + console.log('App starting, log file:', logPath); + // Initialize storage (checks version, resets if needed) storage.initializeStorage(); @@ -31,10 +36,14 @@ app.whenReady().then(async () => { setupAIProviderIpcHandlers(geminiSessionRef); setupStorageIpcHandlers(); setupGeneralIpcHandlers(); + + // Add handler to get log path from renderer + ipcMain.handle('get-log-path', () => getLogPath()); }); app.on('window-all-closed', () => { stopMacOSAudioCapture(); + closeLogger(); if (process.platform !== 'darwin') { app.quit(); } @@ -42,6 +51,7 @@ app.on('window-all-closed', () => { app.on('before-quit', () => { stopMacOSAudioCapture(); + closeLogger(); }); app.on('activate', () => { @@ -284,6 +294,18 @@ function setupGeneralIpcHandlers() { return app.getVersion(); }); + ipcMain.handle('open-logs-folder', async () => { + try { + const logPath = getLogPath(); + const logsDir = require('path').dirname(logPath); + await shell.openPath(logsDir); + return { success: true, path: logsDir }; + } catch (error) { + console.error('Error opening logs folder:', error); + return { success: false, error: error.message }; + } + }); + ipcMain.handle('quit-application', async event => { try { stopMacOSAudioCapture(); diff --git a/src/utils/gemini.js b/src/utils/gemini.js index 36ce507..e0f539e 100644 --- a/src/utils/gemini.js +++ b/src/utils/gemini.js @@ -418,10 +418,12 @@ async function startMacOSAudioCapture(geminiSessionRef) { // Kill any existing SystemAudioDump processes first await killExistingSystemAudioDump(); - console.log('Starting macOS audio capture with SystemAudioDump...'); + console.log('=== Starting macOS audio capture ==='); + sendToRenderer('update-status', 'Starting audio capture...'); const { app } = require('electron'); const path = require('path'); + const fs = require('fs'); let systemAudioPath; if (app.isPackaged) { @@ -430,7 +432,35 @@ async function startMacOSAudioCapture(geminiSessionRef) { systemAudioPath = path.join(__dirname, '../assets', 'SystemAudioDump'); } - console.log('SystemAudioDump path:', systemAudioPath); + console.log('SystemAudioDump config:', { + path: systemAudioPath, + isPackaged: app.isPackaged, + resourcesPath: process.resourcesPath, + exists: fs.existsSync(systemAudioPath), + }); + + // Check if file exists + if (!fs.existsSync(systemAudioPath)) { + console.error('FATAL: SystemAudioDump not found at:', systemAudioPath); + sendToRenderer('update-status', 'Error: Audio binary not found'); + return false; + } + + // Check and fix executable permissions + try { + fs.accessSync(systemAudioPath, fs.constants.X_OK); + console.log('SystemAudioDump is executable'); + } catch (err) { + console.warn('SystemAudioDump not executable, fixing permissions...'); + try { + fs.chmodSync(systemAudioPath, 0o755); + console.log('Fixed executable permissions'); + } catch (chmodErr) { + console.error('Failed to fix permissions:', chmodErr); + sendToRenderer('update-status', 'Error: Cannot execute audio binary'); + return false; + } + } const spawnOptions = { stdio: ['ignore', 'pipe', 'pipe'], @@ -439,10 +469,12 @@ async function startMacOSAudioCapture(geminiSessionRef) { }, }; + console.log('Spawning SystemAudioDump...'); systemAudioProc = spawn(systemAudioPath, [], spawnOptions); if (!systemAudioProc.pid) { - console.error('Failed to start SystemAudioDump'); + console.error('FATAL: Failed to start SystemAudioDump - no PID'); + sendToRenderer('update-status', 'Error: Audio capture failed to start'); return false; } @@ -455,8 +487,16 @@ async function startMacOSAudioCapture(geminiSessionRef) { const CHUNK_SIZE = SAMPLE_RATE * BYTES_PER_SAMPLE * CHANNELS * CHUNK_DURATION; let audioBuffer = Buffer.alloc(0); + let chunkCount = 0; + let firstDataReceived = false; systemAudioProc.stdout.on('data', data => { + if (!firstDataReceived) { + firstDataReceived = true; + console.log('First audio data received! Size:', data.length); + sendToRenderer('update-status', 'Listening...'); + } + audioBuffer = Buffer.concat([audioBuffer, data]); while (audioBuffer.length >= CHUNK_SIZE) { @@ -467,6 +507,11 @@ async function startMacOSAudioCapture(geminiSessionRef) { const base64Data = monoChunk.toString('base64'); sendAudioToGemini(base64Data, geminiSessionRef); + chunkCount++; + if (chunkCount % 100 === 0) { + console.log(`Audio: ${chunkCount} chunks processed`); + } + if (process.env.DEBUG_AUDIO) { console.log(`Processed audio chunk: ${chunk.length} bytes`); saveDebugAudio(monoChunk, 'system_audio'); @@ -480,16 +525,24 @@ async function startMacOSAudioCapture(geminiSessionRef) { }); systemAudioProc.stderr.on('data', data => { - console.error('SystemAudioDump stderr:', data.toString()); + const msg = data.toString(); + console.error('SystemAudioDump stderr:', msg); + if (msg.toLowerCase().includes('error')) { + sendToRenderer('update-status', 'Audio error: ' + msg.substring(0, 50)); + } }); systemAudioProc.on('close', code => { - console.log('SystemAudioDump process closed with code:', code); + console.log('SystemAudioDump closed with code:', code, 'chunks processed:', chunkCount); + if (code !== 0 && code !== null) { + sendToRenderer('update-status', `Audio stopped (exit: ${code})`); + } systemAudioProc = null; }); systemAudioProc.on('error', err => { - console.error('SystemAudioDump process error:', err); + console.error('SystemAudioDump spawn error:', err.message); + sendToRenderer('update-status', 'Audio error: ' + err.message); systemAudioProc = null; }); diff --git a/src/utils/logger.js b/src/utils/logger.js new file mode 100644 index 0000000..20f81c7 --- /dev/null +++ b/src/utils/logger.js @@ -0,0 +1,97 @@ +const fs = require('fs'); +const path = require('path'); +const { app } = require('electron'); + +let logFile = null; +let logPath = null; + +function getLogPath() { + if (logPath) return logPath; + + const userDataPath = app.getPath('userData'); + const logsDir = path.join(userDataPath, 'logs'); + + // Create logs directory if it doesn't exist + if (!fs.existsSync(logsDir)) { + fs.mkdirSync(logsDir, { recursive: true }); + } + + // Create log file with timestamp + const timestamp = new Date().toISOString().split('T')[0]; + logPath = path.join(logsDir, `app-${timestamp}.log`); + + return logPath; +} + +function initLogger() { + try { + const filePath = getLogPath(); + logFile = fs.createWriteStream(filePath, { flags: 'a' }); + + const startMsg = `\n${'='.repeat(60)}\nApp started at ${new Date().toISOString()}\nPlatform: ${process.platform}, Arch: ${process.arch}\nElectron: ${process.versions.electron}, Node: ${process.versions.node}\nPackaged: ${app.isPackaged}\n${'='.repeat(60)}\n`; + logFile.write(startMsg); + + // Override console methods to also write to file + const originalLog = console.log; + const originalError = console.error; + const originalWarn = console.warn; + + console.log = (...args) => { + originalLog.apply(console, args); + writeLog('INFO', args); + }; + + console.error = (...args) => { + originalError.apply(console, args); + writeLog('ERROR', args); + }; + + console.warn = (...args) => { + originalWarn.apply(console, args); + writeLog('WARN', args); + }; + + console.log('Logger initialized, writing to:', filePath); + + return filePath; + } catch (err) { + console.error('Failed to initialize logger:', err); + return null; + } +} + +function writeLog(level, args) { + if (!logFile) return; + + try { + const timestamp = new Date().toISOString(); + const message = args.map(arg => { + if (typeof arg === 'object') { + try { + return JSON.stringify(arg, null, 2); + } catch { + return String(arg); + } + } + return String(arg); + }).join(' '); + + logFile.write(`[${timestamp}] [${level}] ${message}\n`); + } catch (err) { + // Silently fail - don't want logging errors to crash the app + } +} + +function closeLogger() { + if (logFile) { + logFile.write(`\nApp closed at ${new Date().toISOString()}\n`); + logFile.end(); + logFile = null; + } +} + +module.exports = { + initLogger, + closeLogger, + getLogPath, +}; diff --git a/src/utils/openai-sdk.js b/src/utils/openai-sdk.js index 8ca6164..f230d40 100644 --- a/src/utils/openai-sdk.js +++ b/src/utils/openai-sdk.js @@ -441,9 +441,11 @@ async function startMacOSAudioCapture() { // Kill any existing SystemAudioDump processes first await killExistingSystemAudioDump(); - console.log('Starting macOS audio capture with SystemAudioDump for OpenAI SDK...'); + console.log('=== Starting macOS audio capture (OpenAI SDK) ==='); + sendToRenderer('update-status', 'Starting audio capture...'); const { app } = require('electron'); + const fs = require('fs'); let systemAudioPath; if (app.isPackaged) { @@ -452,7 +454,35 @@ async function startMacOSAudioCapture() { systemAudioPath = path.join(__dirname, '../assets', 'SystemAudioDump'); } - console.log('SystemAudioDump path:', systemAudioPath); + console.log('SystemAudioDump config:', { + path: systemAudioPath, + isPackaged: app.isPackaged, + resourcesPath: process.resourcesPath, + exists: fs.existsSync(systemAudioPath), + }); + + // Check if file exists + if (!fs.existsSync(systemAudioPath)) { + console.error('FATAL: SystemAudioDump not found at:', systemAudioPath); + sendToRenderer('update-status', 'Error: Audio binary not found'); + return false; + } + + // Check and fix executable permissions + try { + fs.accessSync(systemAudioPath, fs.constants.X_OK); + console.log('SystemAudioDump is executable'); + } catch (err) { + console.warn('SystemAudioDump not executable, fixing permissions...'); + try { + fs.chmodSync(systemAudioPath, 0o755); + console.log('Fixed executable permissions'); + } catch (chmodErr) { + console.error('Failed to fix permissions:', chmodErr); + sendToRenderer('update-status', 'Error: Cannot execute audio binary'); + return false; + } + } const spawnOptions = { stdio: ['ignore', 'pipe', 'pipe'], @@ -461,10 +491,12 @@ async function startMacOSAudioCapture() { }, }; + console.log('Spawning SystemAudioDump...'); systemAudioProc = spawn(systemAudioPath, [], spawnOptions); if (!systemAudioProc.pid) { - console.error('Failed to start SystemAudioDump'); + console.error('FATAL: Failed to start SystemAudioDump - no PID'); + sendToRenderer('update-status', 'Error: Audio capture failed to start'); return false; } @@ -476,8 +508,16 @@ async function startMacOSAudioCapture() { const CHUNK_SIZE = SAMPLE_RATE * BYTES_PER_SAMPLE * CHANNELS * CHUNK_DURATION; let tempBuffer = Buffer.alloc(0); + let chunkCount = 0; + let firstDataReceived = false; systemAudioProc.stdout.on('data', data => { + if (!firstDataReceived) { + firstDataReceived = true; + console.log('First audio data received! Size:', data.length); + sendToRenderer('update-status', 'Listening...'); + } + tempBuffer = Buffer.concat([tempBuffer, data]); while (tempBuffer.length >= CHUNK_SIZE) { @@ -489,6 +529,11 @@ async function startMacOSAudioCapture() { // Add to audio buffer for transcription audioBuffer = Buffer.concat([audioBuffer, monoChunk]); + + chunkCount++; + if (chunkCount % 100 === 0) { + console.log(`Audio: ${chunkCount} chunks processed, buffer size: ${audioBuffer.length}`); + } } // Limit buffer size (max 30 seconds of audio) @@ -499,21 +544,33 @@ async function startMacOSAudioCapture() { }); systemAudioProc.stderr.on('data', data => { - console.error('SystemAudioDump stderr:', data.toString()); + const msg = data.toString(); + console.error('SystemAudioDump stderr:', msg); + if (msg.toLowerCase().includes('error')) { + sendToRenderer('update-status', 'Audio error: ' + msg.substring(0, 50)); + } }); - systemAudioProc.on('close', code => { - console.log('SystemAudioDump process closed with code:', code); + systemAudioProc.on('close', (code, signal) => { + console.log('SystemAudioDump closed:', { code, signal, chunksProcessed: chunkCount, tempBufferSize: tempBuffer.length }); + if (code !== 0 && code !== null) { + sendToRenderer('update-status', `Audio stopped (exit: ${code}, signal: ${signal})`); + } systemAudioProc = null; stopTranscriptionTimer(); }); systemAudioProc.on('error', err => { - console.error('SystemAudioDump process error:', err); + console.error('SystemAudioDump spawn error:', err.message, err.stack); + sendToRenderer('update-status', 'Audio error: ' + err.message); systemAudioProc = null; stopTranscriptionTimer(); }); + systemAudioProc.on('exit', (code, signal) => { + console.log('SystemAudioDump exit event:', { code, signal }); + }); + // Start periodic transcription startTranscriptionTimer();