const fs = require("fs"); const path = require("path"); const os = require("os"); const CONFIG_VERSION = 1; // Default values const DEFAULT_CONFIG = { configVersion: CONFIG_VERSION, onboarded: false, layout: "normal", }; const DEFAULT_CREDENTIALS = { apiKey: "", groqApiKey: "", openaiCompatibleApiKey: "", openaiCompatibleBaseUrl: "", openaiCompatibleModel: "", }; const DEFAULT_PREFERENCES = { customPrompt: "", selectedProfile: "interview", selectedLanguage: "en-US", selectedScreenshotInterval: "5", selectedImageQuality: "medium", advancedMode: false, audioMode: "speaker_only", fontSize: "medium", backgroundTransparency: 0.8, googleSearchEnabled: false, responseProvider: "gemini", ollamaHost: "http://127.0.0.1:11434", ollamaModel: "llama3.1", whisperModel: "Xenova/whisper-small", whisperDevice: "", // '' = auto-detect, 'cpu' = native, 'wasm' = compatible }; const DEFAULT_KEYBINDS = null; // null means use system defaults const DEFAULT_LIMITS = { data: [], // Array of { date: 'YYYY-MM-DD', flash: { count }, flashLite: { count }, groq: { 'qwen3-32b': { chars, limit }, 'gpt-oss-120b': { chars, limit }, 'gpt-oss-20b': { chars, limit } }, gemini: { 'gemma-3-27b-it': { chars } } } }; // Get the config directory path based on OS function getConfigDir() { const platform = os.platform(); let configDir; if (platform === "win32") { configDir = path.join( os.homedir(), "AppData", "Roaming", "cheating-daddy-config", ); } else if (platform === "darwin") { configDir = path.join( os.homedir(), "Library", "Application Support", "cheating-daddy-config", ); } else { configDir = path.join(os.homedir(), ".config", "cheating-daddy-config"); } return configDir; } // File paths function getConfigPath() { return path.join(getConfigDir(), "config.json"); } function getCredentialsPath() { return path.join(getConfigDir(), "credentials.json"); } function getPreferencesPath() { return path.join(getConfigDir(), "preferences.json"); } function getKeybindsPath() { return path.join(getConfigDir(), "keybinds.json"); } function getLimitsPath() { return path.join(getConfigDir(), "limits.json"); } function getHistoryDir() { return path.join(getConfigDir(), "history"); } // Helper to read JSON file safely function readJsonFile(filePath, defaultValue) { try { if (fs.existsSync(filePath)) { const data = fs.readFileSync(filePath, "utf8"); return JSON.parse(data); } } catch (error) { console.warn(`Error reading ${filePath}:`, error.message); } return defaultValue; } // Helper to write JSON file safely function writeJsonFile(filePath, data) { try { const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8"); return true; } catch (error) { console.error(`Error writing ${filePath}:`, error.message); return false; } } // Check if we need to reset (no configVersion or wrong version) function needsReset() { const configPath = getConfigPath(); if (!fs.existsSync(configPath)) { return true; } try { const config = JSON.parse(fs.readFileSync(configPath, "utf8")); return !config.configVersion || config.configVersion !== CONFIG_VERSION; } catch { return true; } } // Wipe and reinitialize the config directory function resetConfigDir() { const configDir = getConfigDir(); console.log("Resetting config directory..."); // Remove existing directory if it exists if (fs.existsSync(configDir)) { fs.rmSync(configDir, { recursive: true, force: true }); } // Create fresh directory structure fs.mkdirSync(configDir, { recursive: true }); fs.mkdirSync(getHistoryDir(), { recursive: true }); // Initialize with defaults writeJsonFile(getConfigPath(), DEFAULT_CONFIG); writeJsonFile(getCredentialsPath(), DEFAULT_CREDENTIALS); writeJsonFile(getPreferencesPath(), DEFAULT_PREFERENCES); console.log("Config directory initialized with defaults"); } // Initialize storage - call this on app startup function initializeStorage() { if (needsReset()) { resetConfigDir(); } else { // Ensure history directory exists const historyDir = getHistoryDir(); if (!fs.existsSync(historyDir)) { fs.mkdirSync(historyDir, { recursive: true }); } } } // ============ CONFIG ============ function getConfig() { return readJsonFile(getConfigPath(), DEFAULT_CONFIG); } function setConfig(config) { const current = getConfig(); const updated = { ...current, ...config, configVersion: CONFIG_VERSION }; return writeJsonFile(getConfigPath(), updated); } function updateConfig(key, value) { const config = getConfig(); config[key] = value; return writeJsonFile(getConfigPath(), config); } // ============ CREDENTIALS ============ function getCredentials() { return readJsonFile(getCredentialsPath(), DEFAULT_CREDENTIALS); } function setCredentials(credentials) { const current = getCredentials(); const updated = { ...current, ...credentials }; return writeJsonFile(getCredentialsPath(), updated); } function getApiKey() { return getCredentials().apiKey || ""; } function setApiKey(apiKey) { return setCredentials({ apiKey }); } function getGroqApiKey() { return getCredentials().groqApiKey || ""; } function setGroqApiKey(groqApiKey) { return setCredentials({ groqApiKey }); } function getOpenAICompatibleConfig() { const creds = getCredentials(); return { apiKey: creds.openaiCompatibleApiKey || "", baseUrl: creds.openaiCompatibleBaseUrl || "", model: creds.openaiCompatibleModel || "", }; } function setOpenAICompatibleConfig(apiKey, baseUrl, model) { return setCredentials({ openaiCompatibleApiKey: apiKey, openaiCompatibleBaseUrl: baseUrl, openaiCompatibleModel: model, }); } // ============ PREFERENCES ============ function getPreferences() { const saved = readJsonFile(getPreferencesPath(), {}); return { ...DEFAULT_PREFERENCES, ...saved }; } function setPreferences(preferences) { const current = getPreferences(); const updated = { ...current, ...preferences }; return writeJsonFile(getPreferencesPath(), updated); } function updatePreference(key, value) { const preferences = getPreferences(); preferences[key] = value; return writeJsonFile(getPreferencesPath(), preferences); } // ============ KEYBINDS ============ function getKeybinds() { return readJsonFile(getKeybindsPath(), DEFAULT_KEYBINDS); } function setKeybinds(keybinds) { return writeJsonFile(getKeybindsPath(), keybinds); } // ============ LIMITS (Rate Limiting) ============ function getLimits() { return readJsonFile(getLimitsPath(), DEFAULT_LIMITS); } function setLimits(limits) { return writeJsonFile(getLimitsPath(), limits); } function getTodayDateString() { const now = new Date(); return now.toISOString().split("T")[0]; // YYYY-MM-DD } function getTodayLimits() { const limits = getLimits(); const today = getTodayDateString(); // Find today's entry const todayEntry = limits.data.find((entry) => entry.date === today); if (todayEntry) { // ensure new fields exist if (!todayEntry.groq) { todayEntry.groq = { "qwen3-32b": { chars: 0, limit: 1500000 }, "gpt-oss-120b": { chars: 0, limit: 600000 }, "gpt-oss-20b": { chars: 0, limit: 600000 }, "kimi-k2-instruct": { chars: 0, limit: 600000 }, }; } if (!todayEntry.gemini) { todayEntry.gemini = { "gemma-3-27b-it": { chars: 0 }, }; } setLimits(limits); return todayEntry; } // No entry for today - clean old entries and create new one limits.data = limits.data.filter((entry) => entry.date === today); const newEntry = { date: today, flash: { count: 0 }, flashLite: { count: 0 }, groq: { "qwen3-32b": { chars: 0, limit: 1500000 }, "gpt-oss-120b": { chars: 0, limit: 600000 }, "gpt-oss-20b": { chars: 0, limit: 600000 }, "kimi-k2-instruct": { chars: 0, limit: 600000 }, }, gemini: { "gemma-3-27b-it": { chars: 0 }, }, }; limits.data.push(newEntry); setLimits(limits); return newEntry; } function incrementLimitCount(model) { const limits = getLimits(); const today = getTodayDateString(); // Find or create today's entry let todayEntry = limits.data.find((entry) => entry.date === today); if (!todayEntry) { // Clean old entries and create new one limits.data = []; todayEntry = { date: today, flash: { count: 0 }, flashLite: { count: 0 }, }; limits.data.push(todayEntry); } else { // Clean old entries, keep only today limits.data = limits.data.filter((entry) => entry.date === today); } // Increment the appropriate model count if (model === "gemini-2.5-flash") { todayEntry.flash.count++; } else if (model === "gemini-2.5-flash-lite") { todayEntry.flashLite.count++; } setLimits(limits); return todayEntry; } function incrementCharUsage(provider, model, charCount) { getTodayLimits(); const limits = getLimits(); const today = getTodayDateString(); const todayEntry = limits.data.find((entry) => entry.date === today); if (todayEntry[provider] && todayEntry[provider][model]) { todayEntry[provider][model].chars += charCount; setLimits(limits); } return todayEntry; } function getAvailableModel() { const todayLimits = getTodayLimits(); // RPD limits: flash = 20, flash-lite = 20 // After both exhausted, fall back to flash (for paid API users) if (todayLimits.flash.count < 20) { return "gemini-2.5-flash"; } else if (todayLimits.flashLite.count < 20) { return "gemini-2.5-flash-lite"; } return "gemini-2.5-flash"; // Default to flash for paid API users } function getModelForToday() { const todayEntry = getTodayLimits(); const groq = todayEntry.groq; if (groq["qwen3-32b"].chars < groq["qwen3-32b"].limit) { return "qwen/qwen3-32b"; } if (groq["gpt-oss-120b"].chars < groq["gpt-oss-120b"].limit) { return "openai/gpt-oss-120b"; } if (groq["gpt-oss-20b"].chars < groq["gpt-oss-20b"].limit) { return "openai/gpt-oss-20b"; } if (groq["kimi-k2-instruct"].chars < groq["kimi-k2-instruct"].limit) { return "moonshotai/kimi-k2-instruct"; } // All limits exhausted return null; } // ============ HISTORY ============ function getSessionPath(sessionId) { return path.join(getHistoryDir(), `${sessionId}.json`); } function saveSession(sessionId, data) { const sessionPath = getSessionPath(sessionId); // Load existing session to preserve metadata const existingSession = readJsonFile(sessionPath, null); const sessionData = { sessionId, createdAt: existingSession?.createdAt || parseInt(sessionId), lastUpdated: Date.now(), // Profile context - set once when session starts profile: data.profile || existingSession?.profile || null, customPrompt: data.customPrompt || existingSession?.customPrompt || null, // Conversation data conversationHistory: data.conversationHistory || existingSession?.conversationHistory || [], screenAnalysisHistory: data.screenAnalysisHistory || existingSession?.screenAnalysisHistory || [], }; return writeJsonFile(sessionPath, sessionData); } function getSession(sessionId) { return readJsonFile(getSessionPath(sessionId), null); } function getAllSessions() { const historyDir = getHistoryDir(); try { if (!fs.existsSync(historyDir)) { return []; } const files = fs .readdirSync(historyDir) .filter((f) => f.endsWith(".json")) .sort((a, b) => { // Sort by timestamp descending (newest first) const tsA = parseInt(a.replace(".json", "")); const tsB = parseInt(b.replace(".json", "")); return tsB - tsA; }); return files .map((file) => { const sessionId = file.replace(".json", ""); const data = readJsonFile(path.join(historyDir, file), null); if (data) { return { sessionId, createdAt: data.createdAt, lastUpdated: data.lastUpdated, messageCount: data.conversationHistory?.length || 0, screenAnalysisCount: data.screenAnalysisHistory?.length || 0, profile: data.profile || null, customPrompt: data.customPrompt || null, }; } return null; }) .filter(Boolean); } catch (error) { console.error("Error reading sessions:", error.message); return []; } } function deleteSession(sessionId) { const sessionPath = getSessionPath(sessionId); try { if (fs.existsSync(sessionPath)) { fs.unlinkSync(sessionPath); return true; } } catch (error) { console.error("Error deleting session:", error.message); } return false; } function deleteAllSessions() { const historyDir = getHistoryDir(); try { if (fs.existsSync(historyDir)) { const files = fs .readdirSync(historyDir) .filter((f) => f.endsWith(".json")); files.forEach((file) => { fs.unlinkSync(path.join(historyDir, file)); }); } return true; } catch (error) { console.error("Error deleting all sessions:", error.message); return false; } } // ============ CLEAR ALL DATA ============ function clearAllData() { resetConfigDir(); return true; } module.exports = { // Initialization initializeStorage, getConfigDir, // Config getConfig, setConfig, updateConfig, // Credentials getCredentials, setCredentials, getApiKey, setApiKey, getGroqApiKey, setGroqApiKey, getOpenAICompatibleConfig, setOpenAICompatibleConfig, // Preferences getPreferences, setPreferences, updatePreference, // Keybinds getKeybinds, setKeybinds, // Limits (Rate Limiting) getLimits, setLimits, getTodayLimits, incrementLimitCount, getAvailableModel, incrementCharUsage, getModelForToday, // History saveSession, getSession, getAllSessions, deleteSession, deleteAllSessions, // Clear all clearAllData, };