UNPKG

@profullstack/lead-generator

Version:

A CLI tool for sending mass lead emails to prospective clients from CSV files with AI-powered personalization

227 lines (200 loc) 6.36 kB
/** * Configuration Manager * Handles user configuration for lead generator */ import fs from 'fs'; import path from 'path'; import os from 'os'; /** * Get the path to the configuration file * @returns {string} Full path to config.json */ export function getConfigPath() { return path.join(os.homedir(), '.config', 'lead-generator', 'config.json'); } /** * Get default configuration structure * @returns {object} Default configuration object */ export function getDefaultConfig() { return { email: { mailgun: { apiKey: '', domain: '' }, from: '', replyTo: '', batchSize: 100, delayBetweenEmails: 1000 }, voice: { blandAI: { apiKey: '', phoneNumber: '' }, batchSize: 50, delayBetweenCalls: 5000, maxCallDuration: 300 }, ai: { openai: { apiKey: '', model: 'gpt-4', maxTokens: 1000, temperature: 0.7 } }, general: { retryAttempts: 3, timeout: 30000, logLevel: 'info', batchSize: 100 }, webhooks: { port: 3000, enabled: false } }; } /** * Validate configuration object * @param {object} config - Configuration to validate * @returns {object} Validation result with valid flag and errors array */ export function validateConfig(config) { const errors = []; // Check required fields if (!config?.email?.mailgun?.apiKey || config.email.mailgun.apiKey.trim() === '') { errors.push('Mailgun API key is required'); } if (!config?.email?.mailgun?.domain || config.email.mailgun.domain.trim() === '') { errors.push('Mailgun domain is required'); } if (!config?.ai?.openai?.apiKey || config.ai.openai.apiKey.trim() === '') { errors.push('OpenAI API key is required'); } // Validate email format if provided if (config?.email?.from && !isValidEmail(config.email.from)) { errors.push('Invalid "from" email address format'); } if (config?.email?.replyTo && !isValidEmail(config.email.replyTo)) { errors.push('Invalid "replyTo" email address format'); } // Validate numeric values if (config?.email?.batchSize && (!Number.isInteger(config.email.batchSize) || config.email.batchSize < 1)) { errors.push('Email batch size must be a positive integer'); } if (config?.voice?.batchSize && (!Number.isInteger(config.voice.batchSize) || config.voice.batchSize < 1)) { errors.push('Voice batch size must be a positive integer'); } if (config?.general?.retryAttempts && (!Number.isInteger(config.general.retryAttempts) || config.general.retryAttempts < 0)) { errors.push('Retry attempts must be a non-negative integer'); } if (config?.webhooks?.port && (!Number.isInteger(config.webhooks.port) || config.webhooks.port < 1 || config.webhooks.port > 65535)) { errors.push('Webhook port must be between 1 and 65535'); } return { valid: errors.length === 0, errors }; } /** * Load configuration from file * @param {string} [configPath] - Optional custom config path * @returns {object} Configuration object */ export function loadConfig(configPath = getConfigPath()) { try { if (!fs.existsSync(configPath)) { return getDefaultConfig(); } const configData = fs.readFileSync(configPath, 'utf8'); const config = JSON.parse(configData); // Merge with defaults to ensure all fields exist return mergeConfig(getDefaultConfig(), config); } catch (error) { console.warn(`Failed to load config from ${configPath}: ${error.message}`); return getDefaultConfig(); } } /** * Save configuration to file * @param {object} config - Configuration object to save * @param {string} [configPath] - Optional custom config path * @returns {object} Result with success flag and optional error message */ export function saveConfig(config, configPath = getConfigPath()) { try { // Ensure directory exists const configDir = path.dirname(configPath); if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); } // Save config (validation is optional and done elsewhere) fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); return { success: true }; } catch (error) { return { success: false, error: `Failed to save config: ${error.message}` }; } } /** * Merge configuration objects (deep merge) * @param {object} defaultConfig - Default configuration * @param {object} userConfig - User configuration to merge * @returns {object} Merged configuration */ export function mergeConfig(defaultConfig, userConfig) { const merged = JSON.parse(JSON.stringify(defaultConfig)); // Deep clone function deepMerge(target, source) { for (const key in source) { if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) { if (!target[key]) target[key] = {}; deepMerge(target[key], source[key]); } else { target[key] = source[key]; } } } deepMerge(merged, userConfig); return merged; } /** * Check if a string is a valid email address * @param {string} email - Email to validate * @returns {boolean} True if valid email format */ function isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } /** * Get configuration value by dot notation path * @param {object} config - Configuration object * @param {string} path - Dot notation path (e.g., 'email.mailgun.apiKey') * @param {*} defaultValue - Default value if path not found * @returns {*} Configuration value */ export function getConfigValue(config, path, defaultValue = null) { return path.split('.').reduce((obj, key) => obj?.[key], config) ?? defaultValue; } /** * Set configuration value by dot notation path * @param {object} config - Configuration object to modify * @param {string} path - Dot notation path (e.g., 'email.mailgun.apiKey') * @param {*} value - Value to set * @returns {object} Modified configuration object */ export function setConfigValue(config, path, value) { const keys = path.split('.'); const lastKey = keys.pop(); const target = keys.reduce((obj, key) => { if (!obj[key]) obj[key] = {}; return obj[key]; }, config); target[lastKey] = value; return config; }