UNPKG

cerberus-claude-cli

Version:

CLI tool for preparing files and projects for Claude AI. Collect, organize, and analyze source code files to create comprehensive project contexts.

276 lines (235 loc) 7.09 kB
const fs = require('fs-extra'); const path = require('path'); const crypto = require('crypto'); // Import logger but handle potential circular dependency let logger; try { logger = require('./logger'); } catch (e) { // Simple fallback logger in case of circular dependency logger = { error: (msg, err) => console.error(msg, err), info: msg => console.log(msg), warn: msg => console.warn(msg), }; } /** * Simple configuration storage that saves to JSON files */ class SimpleConfig { /** * Create a simple configuration storage * @param {Object} options - Configuration options * @param {string} options.name - Name of the configuration file * @param {string} options.dir - Directory to store the configuration * @param {Object} options.defaults - Default configuration values * @param {string} [options.encryptionKey] - Optional encryption key for sensitive data * @param {number} [options.expiresIn] - Optional expiration time in milliseconds */ constructor(options) { this.name = options.name || 'config'; this.dir = options.dir || path.join(process.cwd(), 'config'); this.defaults = options.defaults || {}; this.encryptionKey = options.encryptionKey || null; this.expiresIn = options.expiresIn || null; this.store = { ...this.defaults }; // Create directory if it doesn't exist fs.ensureDirSync(this.dir); // Try to load existing config this.load(); } /** * Get full path to the config file * @returns {string} - Full path */ getFilePath() { return path.join(this.dir, `${this.name}.json`); } /** * Load configuration from disk */ load() { try { const filePath = this.getFilePath(); if (fs.existsSync(filePath)) { let data = fs.readFileSync(filePath, 'utf8'); // Decrypt if necessary if (this.encryptionKey && this.isEncrypted(data)) { data = this.decrypt(data); } // Parse and merge with defaults const parsed = JSON.parse(data); // Check if the configuration has expired if (this.expiresIn && parsed._timestamp) { const now = Date.now(); const createdAt = parsed._timestamp; const expirationTime = createdAt + this.expiresIn; if (now > expirationTime) { logger.info(`Configuration ${this.name} has expired, using defaults`); this.store = { ...this.defaults }; return; } } this.store = { ...this.defaults, ...parsed, }; } } catch (error) { logger.error(`Error loading configuration from ${this.getFilePath()}:`, error); // If there's an error, use defaults this.store = { ...this.defaults }; } } /** * Save configuration to disk */ save() { try { const filePath = this.getFilePath(); // Add timestamp for expiration check if (this.expiresIn) { this.store._timestamp = Date.now(); } let data = JSON.stringify(this.store, null, 2); // Encrypt if necessary if (this.encryptionKey) { data = this.encrypt(data); } fs.writeFileSync(filePath, data); } catch (error) { logger.error(`Error saving configuration to ${this.getFilePath()}:`, error); } } /** * Check if data is encrypted * @param {string} data - Data to check * @returns {boolean} - Whether the data is encrypted */ isEncrypted(data) { try { const json = JSON.parse(data); return json && json.encrypted === true; } catch (error) { return true; // If we can't parse it as JSON, assume it's encrypted } } /** * Encrypt data * @param {string} data - Data to encrypt * @returns {string} - Encrypted data */ encrypt(data) { try { if (!this.encryptionKey) return data; const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv( 'aes-256-cbc', Buffer.from(this.encryptionKey.slice(0, 32)), iv ); let encrypted = cipher.update(data, 'utf8', 'hex'); encrypted += cipher.final('hex'); // Return JSON object with encrypted data return JSON.stringify({ encrypted: true, iv: iv.toString('hex'), data: encrypted, }); } catch (error) { logger.error('Encryption error:', error); return data; // Return original data on error } } /** * Decrypt data * @param {string} encrypted - Encrypted data * @returns {string} - Decrypted data */ decrypt(encrypted) { try { if (!this.encryptionKey) return encrypted; const json = JSON.parse(encrypted); if (!json.encrypted) return encrypted; const iv = Buffer.from(json.iv, 'hex'); const encryptedData = json.data; const decipher = crypto.createDecipheriv( 'aes-256-cbc', Buffer.from(this.encryptionKey.slice(0, 32)), iv ); let decrypted = decipher.update(encryptedData, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } catch (error) { logger.error('Decryption error:', error); return encrypted; // Return original data on error } } /** * Get a configuration value * @param {string} key - Key to get * @param {any} defaultValue - Default value if key doesn't exist * @returns {any} - Configuration value */ get(key, defaultValue = undefined) { const keys = key.split('.'); let value = this.store; for (const k of keys) { if (value === undefined || value === null || typeof value !== 'object') { return defaultValue; } value = value[k]; } return value !== undefined ? value : defaultValue; } /** * Set a configuration value * @param {string} key - Key to set * @param {any} value - Value to set */ set(key, value) { const keys = key.split('.'); let target = this.store; // Navigate to the right nested object for (let i = 0; i < keys.length - 1; i++) { const k = keys[i]; if (target[k] === undefined || target[k] === null || typeof target[k] !== 'object') { target[k] = {}; } target = target[k]; } // Set the value target[keys[keys.length - 1]] = value; // Save to disk this.save(); } /** * Delete a configuration value * @param {string} key - Key to delete */ delete(key) { const keys = key.split('.'); let target = this.store; // Navigate to the right nested object for (let i = 0; i < keys.length - 1; i++) { const k = keys[i]; if (target[k] === undefined || target[k] === null || typeof target[k] !== 'object') { return; // Key path doesn't exist } target = target[k]; } // Delete the value delete target[keys[keys.length - 1]]; // Save to disk this.save(); } /** * Clear all configuration */ clear() { this.store = { ...this.defaults }; this.save(); } } module.exports = SimpleConfig;