blue-beatle
Version:
๐ค AI-Powered Development Assistant - Intelligent code analysis, problem solving, and terminal automation using Gemini API
651 lines (579 loc) โข 23.2 kB
JavaScript
/**
* โ๏ธ Config Manager - Configuration and settings management
* Handles API keys, user preferences, and application settings
*/
const fs = require('fs-extra');
const path = require('path');
const os = require('os');
const chalk = require('chalk');
const inquirer = require('inquirer');
const crypto = require('crypto');
class ConfigManager {
constructor() {
this.configDir = path.join(os.homedir(), '.blue-beatle');
this.configFile = path.join(this.configDir, 'config.json');
this.secretsFile = path.join(this.configDir, 'secrets.enc');
this.defaultConfig = {
version: '1.2.0',
user: {
name: '',
email: '',
preferences: {
theme: 'default',
verboseOutput: false,
autoUpdate: true,
telemetry: false
}
},
ai: {
provider: 'gemini',
model: 'gemini-2.0-flash',
maxTokens: 4096,
temperature: 0.7,
timeout: 30000
},
terminal: {
safeMode: true,
logCommands: false,
confirmDangerous: true,
historySize: 1000
},
analysis: {
autoFix: false,
watchMode: false,
notifications: true,
reportFormat: 'table'
},
project: {
defaultTemplate: 'node',
autoGitInit: true,
createReadme: true,
setupDocker: false
}
};
this.config = { ...this.defaultConfig };
this.secrets = {
geminiApiKey: 'AIzaSyD4QvJ_DEBDxOzM4nGlfSy9jYMm6ac7plI'
};
}
async initialize() {
try {
await fs.ensureDir(this.configDir);
await this.loadConfig();
await this.loadSecrets();
return true;
} catch (error) {
console.error(chalk.red('โ Failed to initialize config:'), error.message);
return false;
}
}
async loadConfig() {
try {
if (await fs.pathExists(this.configFile)) {
const configData = await fs.readJson(this.configFile);
this.config = this.mergeConfig(this.defaultConfig, configData);
} else {
await this.saveConfig();
}
} catch (error) {
console.error(chalk.yellow('โ ๏ธ Using default config due to error:'), error.message);
this.config = { ...this.defaultConfig };
}
}
async loadSecrets() {
try {
if (await fs.pathExists(this.secretsFile)) {
const encryptedData = await fs.readFile(this.secretsFile);
this.secrets = this.decryptSecrets(encryptedData);
}
} catch (error) {
console.error(chalk.yellow('โ ๏ธ Could not load secrets:'), error.message);
this.secrets = {};
}
}
async saveConfig() {
try {
await fs.writeJson(this.configFile, this.config, { spaces: 2 });
} catch (error) {
console.error(chalk.red('โ Failed to save config:'), error.message);
}
}
async saveSecrets() {
try {
const encryptedData = this.encryptSecrets(this.secrets);
await fs.writeFile(this.secretsFile, encryptedData);
} catch (error) {
console.error(chalk.red('โ Failed to save secrets:'), error.message);
}
}
mergeConfig(defaultConfig, userConfig) {
const merged = { ...defaultConfig };
for (const [key, value] of Object.entries(userConfig)) {
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
merged[key] = this.mergeConfig(defaultConfig[key] || {}, value);
} else {
merged[key] = value;
}
}
return merged;
}
encryptSecrets(secrets) {
const algorithm = 'aes-256-cbc';
const key = this.getEncryptionKey();
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipher(algorithm, key);
let encrypted = cipher.update(JSON.stringify(secrets), 'utf8', 'hex');
encrypted += cipher.final('hex');
return Buffer.concat([iv, Buffer.from(encrypted, 'hex')]);
}
decryptSecrets(encryptedData) {
try {
const algorithm = 'aes-256-cbc';
const key = this.getEncryptionKey();
const iv = encryptedData.slice(0, 16);
const encrypted = encryptedData.slice(16);
const decipher = crypto.createDecipher(algorithm, key);
let decrypted = decipher.update(encrypted.toString('hex'), 'hex', 'utf8');
decrypted += decipher.final('utf8');
return JSON.parse(decrypted);
} catch (error) {
console.error(chalk.yellow('โ ๏ธ Could not decrypt secrets, using empty secrets'));
return {};
}
}
getEncryptionKey() {
// Generate a key based on machine-specific information
const machineId = os.hostname() + os.userInfo().username;
return crypto.createHash('sha256').update(machineId).digest();
}
async get(keyPath) {
const keys = keyPath.split('.');
let value = this.config;
for (const key of keys) {
if (value && typeof value === 'object' && key in value) {
value = value[key];
} else {
return undefined;
}
}
// Check if it's a secret reference
if (typeof value === 'string' && value.startsWith('secret:')) {
const secretKey = value.replace('secret:', '');
return this.secrets[secretKey];
}
return value;
}
async set(keyPath, value) {
const keys = keyPath.split('.');
const lastKey = keys.pop();
let target = this.config;
for (const key of keys) {
if (!(key in target) || typeof target[key] !== 'object') {
target[key] = {};
}
target = target[key];
}
target[lastKey] = value;
await this.saveConfig();
}
async setSecret(key, value) {
this.secrets[key] = value;
await this.saveSecrets();
// Update config to reference the secret
if (key === 'geminiApiKey') {
await this.set('gemini.apiKey', `secret:${key}`);
}
}
async handleCommand(options) {
if (options.setupApi) {
await this.setupApiKey();
} else if (options.set) {
await this.setConfigValue(options.set);
} else if (options.get) {
await this.getConfigValue(options.get);
} else if (options.list) {
await this.listConfig();
} else if (options.reset) {
await this.resetConfig();
} else {
await this.showConfigMenu();
}
}
async setupApiKey() {
console.log(chalk.cyan('๐ API Key Setup\n'));
console.log(chalk.blue('To use Blue Beatle AI features, you need a Gemini API key.'));
console.log(chalk.gray('Get your free API key at: https://makersuite.google.com/app/apikey\n'));
const { apiKey } = await inquirer.prompt([{
type: 'password',
name: 'apiKey',
message: 'Enter your Gemini API key:',
mask: '*',
validate: input => {
if (!input.trim()) {
return 'API key is required';
}
if (!input.startsWith('AIza')) {
return 'Invalid Gemini API key format';
}
return true;
}
}]);
await this.setSecret('geminiApiKey', apiKey.trim());
console.log(chalk.green('โ
API key saved securely!'));
// Test the API key
console.log(chalk.blue('๐งช Testing API key...'));
try {
const { GoogleGenerativeAI } = require('@google/generative-ai');
const genAI = new GoogleGenerativeAI(apiKey);
const model = genAI.getGenerativeModel({ model: 'gemini-2.0-flash' });
await model.generateContent('Hello');
console.log(chalk.green('โ
API key is working correctly!'));
} catch (error) {
console.log(chalk.red('โ API key test failed:'), error.message);
console.log(chalk.yellow('๐ก Please check your API key and try again.'));
}
}
async setConfigValue(keyValue) {
const [key, value] = keyValue.split('=');
if (!key || value === undefined) {
console.error(chalk.red('โ Invalid format. Use: key=value'));
return;
}
try {
// Try to parse as JSON for complex values
let parsedValue;
try {
parsedValue = JSON.parse(value);
} catch {
parsedValue = value;
}
await this.set(key, parsedValue);
console.log(chalk.green(`โ
Set ${key} = ${value}`));
} catch (error) {
console.error(chalk.red('โ Failed to set config:'), error.message);
}
}
async getConfigValue(key) {
try {
const value = await this.get(key);
if (value !== undefined) {
console.log(chalk.cyan(`${key}:`), JSON.stringify(value, null, 2));
} else {
console.log(chalk.yellow(`โ ๏ธ Key '${key}' not found`));
}
} catch (error) {
console.error(chalk.red('โ Failed to get config:'), error.message);
}
}
async listConfig() {
console.log(chalk.cyan('โ๏ธ Current Configuration:\n'));
this.printConfigSection('User Settings', this.config.user);
this.printConfigSection('AI Settings', this.config.ai);
this.printConfigSection('Terminal Settings', this.config.terminal);
this.printConfigSection('Analysis Settings', this.config.analysis);
this.printConfigSection('Project Settings', this.config.project);
console.log(chalk.gray('\n๐ Config file location:'), this.configFile);
console.log(chalk.gray('๐ Secrets file location:'), this.secretsFile);
}
printConfigSection(title, section) {
console.log(chalk.blue(`${title}:`));
for (const [key, value] of Object.entries(section)) {
if (typeof value === 'object') {
console.log(chalk.gray(` ${key}:`));
for (const [subKey, subValue] of Object.entries(value)) {
console.log(chalk.gray(` ${subKey}: ${JSON.stringify(subValue)}`));
}
} else {
const displayValue = typeof value === 'string' && value.startsWith('secret:')
? '***hidden***'
: JSON.stringify(value);
console.log(chalk.gray(` ${key}: ${displayValue}`));
}
}
console.log('');
}
async resetConfig() {
const { confirm } = await inquirer.prompt([{
type: 'confirm',
name: 'confirm',
message: chalk.red('โ ๏ธ This will reset all configuration to defaults. Continue?'),
default: false
}]);
if (confirm) {
this.config = { ...this.defaultConfig };
await this.saveConfig();
console.log(chalk.green('โ
Configuration reset to defaults'));
} else {
console.log(chalk.yellow('โญ๏ธ Reset cancelled'));
}
}
async showConfigMenu() {
console.log(chalk.cyan('โ๏ธ Configuration Menu\n'));
const { action } = await inquirer.prompt([{
type: 'list',
name: 'action',
message: 'What would you like to configure?',
choices: [
{ name: '๐ Setup API Key', value: 'api' },
{ name: '๐ค User Settings', value: 'user' },
{ name: '๐ค AI Settings', value: 'ai' },
{ name: 'โก Terminal Settings', value: 'terminal' },
{ name: '๐ Analysis Settings', value: 'analysis' },
{ name: '๐ Project Settings', value: 'project' },
{ name: '๐ View All Settings', value: 'list' },
{ name: '๐ Reset to Defaults', value: 'reset' },
{ name: 'โ Exit', value: 'exit' }
]
}]);
switch (action) {
case 'api':
await this.setupApiKey();
break;
case 'user':
await this.configureUserSettings();
break;
case 'ai':
await this.configureAISettings();
break;
case 'terminal':
await this.configureTerminalSettings();
break;
case 'analysis':
await this.configureAnalysisSettings();
break;
case 'project':
await this.configureProjectSettings();
break;
case 'list':
await this.listConfig();
break;
case 'reset':
await this.resetConfig();
break;
case 'exit':
console.log(chalk.yellow('๐ Configuration menu closed'));
break;
}
}
async configureUserSettings() {
console.log(chalk.cyan('๐ค User Settings\n'));
const userSettings = await inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Your name:',
default: this.config.user.name
},
{
type: 'input',
name: 'email',
message: 'Your email:',
default: this.config.user.email
},
{
type: 'list',
name: 'theme',
message: 'Preferred theme:',
choices: ['default', 'dark', 'light', 'colorful'],
default: this.config.user.preferences.theme
},
{
type: 'confirm',
name: 'verboseOutput',
message: 'Enable verbose output?',
default: this.config.user.preferences.verboseOutput
},
{
type: 'confirm',
name: 'autoUpdate',
message: 'Enable automatic updates?',
default: this.config.user.preferences.autoUpdate
},
{
type: 'confirm',
name: 'telemetry',
message: 'Enable anonymous telemetry?',
default: this.config.user.preferences.telemetry
}
]);
this.config.user.name = userSettings.name;
this.config.user.email = userSettings.email;
this.config.user.preferences.theme = userSettings.theme;
this.config.user.preferences.verboseOutput = userSettings.verboseOutput;
this.config.user.preferences.autoUpdate = userSettings.autoUpdate;
this.config.user.preferences.telemetry = userSettings.telemetry;
await this.saveConfig();
console.log(chalk.green('โ
User settings updated!'));
}
async configureAISettings() {
console.log(chalk.cyan('๐ค AI Settings\n'));
const aiSettings = await inquirer.prompt([
{
type: 'list',
name: 'model',
message: 'AI Model:',
choices: ['gemini-2.5-pro', 'gemini-1.5-pro', 'gemini-1.0-flash', 'gemini-2.0-flash'],
default: this.config.ai.model
},
{
type: 'number',
name: 'maxTokens',
message: 'Max tokens per request:',
default: this.config.ai.maxTokens,
validate: input => input > 0 && input <= 32768
},
{
type: 'number',
name: 'temperature',
message: 'Temperature (0.0-1.0):',
default: this.config.ai.temperature,
validate: input => input >= 0 && input <= 1
},
{
type: 'number',
name: 'timeout',
message: 'Request timeout (ms):',
default: this.config.ai.timeout,
validate: input => input > 0
}
]);
Object.assign(this.config.ai, aiSettings);
await this.saveConfig();
console.log(chalk.green('โ
AI settings updated!'));
}
async configureTerminalSettings() {
console.log(chalk.cyan('โก Terminal Settings\n'));
const terminalSettings = await inquirer.prompt([
{
type: 'confirm',
name: 'safeMode',
message: 'Enable safe mode (confirm dangerous commands)?',
default: this.config.terminal.safeMode
},
{
type: 'confirm',
name: 'logCommands',
message: 'Log all executed commands?',
default: this.config.terminal.logCommands
},
{
type: 'confirm',
name: 'confirmDangerous',
message: 'Confirm before executing dangerous commands?',
default: this.config.terminal.confirmDangerous
},
{
type: 'number',
name: 'historySize',
message: 'Command history size:',
default: this.config.terminal.historySize,
validate: input => input > 0
}
]);
Object.assign(this.config.terminal, terminalSettings);
await this.saveConfig();
console.log(chalk.green('โ
Terminal settings updated!'));
}
async configureAnalysisSettings() {
console.log(chalk.cyan('๐ Analysis Settings\n'));
const analysisSettings = await inquirer.prompt([
{
type: 'confirm',
name: 'autoFix',
message: 'Enable automatic fixing of issues?',
default: this.config.analysis.autoFix
},
{
type: 'confirm',
name: 'watchMode',
message: 'Enable file watching by default?',
default: this.config.analysis.watchMode
},
{
type: 'confirm',
name: 'notifications',
message: 'Enable notifications for issues?',
default: this.config.analysis.notifications
},
{
type: 'list',
name: 'reportFormat',
message: 'Default report format:',
choices: ['table', 'json', 'markdown'],
default: this.config.analysis.reportFormat
}
]);
Object.assign(this.config.analysis, analysisSettings);
await this.saveConfig();
console.log(chalk.green('โ
Analysis settings updated!'));
}
async configureProjectSettings() {
console.log(chalk.cyan('๐ Project Settings\n'));
const projectSettings = await inquirer.prompt([
{
type: 'list',
name: 'defaultTemplate',
message: 'Default project template:',
choices: ['react', 'node', 'vue', 'angular', 'python', 'rust', 'go'],
default: this.config.project.defaultTemplate
},
{
type: 'confirm',
name: 'autoGitInit',
message: 'Automatically initialize Git repository?',
default: this.config.project.autoGitInit
},
{
type: 'confirm',
name: 'createReadme',
message: 'Automatically create README.md?',
default: this.config.project.createReadme
},
{
type: 'confirm',
name: 'setupDocker',
message: 'Setup Docker files by default?',
default: this.config.project.setupDocker
}
]);
Object.assign(this.config.project, projectSettings);
await this.saveConfig();
console.log(chalk.green('โ
Project settings updated!'));
}
async runSetup() {
console.log(chalk.cyan('๐ MacAdida Initial Setup\n'));
console.log(chalk.blue('Welcome to MacAdida AI-Powered Development Assistant!'));
console.log(chalk.gray('Let\'s configure your environment for the best experience.\n'));
// User information
await this.configureUserSettings();
console.log('');
// API key setup
const { setupApi } = await inquirer.prompt([{
type: 'confirm',
name: 'setupApi',
message: 'Would you like to setup your Gemini API key now?',
default: true
}]);
if (setupApi) {
await this.setupApiKey();
} else {
console.log(chalk.yellow('โ ๏ธ You can setup your API key later with: blue-beatle config --setup-api'));
}
console.log('');
// Quick preferences
const { quickSetup } = await inquirer.prompt([{
type: 'confirm',
name: 'quickSetup',
message: 'Configure additional preferences now?',
default: false
}]);
if (quickSetup) {
await this.configureTerminalSettings();
await this.configureAnalysisSettings();
}
console.log(chalk.green('\n๐ Setup complete! Blue Beatle is ready to use.'));
console.log(chalk.cyan('๐ก Try: blue-beatle ai "help me analyze my code"'));
console.log(chalk.cyan('๐ก Or: blue-beatle interactive'));
}
}
module.exports = ConfigManager;