From ce677f441d4261f6409e7e50240aaff3e81701fd Mon Sep 17 00:00:00 2001 From: David Snelling Date: Thu, 7 Aug 2025 13:55:42 -0700 Subject: [PATCH] chore: simplify build for v0.55.0 release - Temporarily remove Cortex CLI dependencies to fix build - Keep core coordination methods in BrainyData - Cortex CLI will be added in v0.56 as separate package --- package.json | 7 - src/brainyData.ts | 69 +--- src/cortex/cli.ts | 225 ----------- src/cortex/commands/index.ts | 713 ----------------------------------- src/cortex/config.ts | 410 -------------------- tsconfig.json | 1 + 6 files changed, 10 insertions(+), 1415 deletions(-) delete mode 100644 src/cortex/cli.ts delete mode 100644 src/cortex/commands/index.ts delete mode 100644 src/cortex/config.ts diff --git a/package.json b/package.json index df29c3f8..2a0cb23a 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,6 @@ "module": "dist/index.js", "types": "dist/index.d.ts", "type": "module", - "bin": { - "cortex": "./dist/cortex/cli.js" - }, "sideEffects": [ "./dist/setup.js", "./dist/utils/textEncoding.js", @@ -161,11 +158,7 @@ "@huggingface/transformers": "^3.1.0", "@smithy/node-http-handler": "^4.1.1", "buffer": "^6.0.3", - "chalk": "^5.3.0", - "cli-table3": "^0.6.3", - "commander": "^11.1.0", "dotenv": "^16.4.5", - "ora": "^7.0.1", "uuid": "^9.0.1" }, "prettier": { diff --git a/src/brainyData.ts b/src/brainyData.ts index 5d8fb103..81e4d546 100644 --- a/src/brainyData.ts +++ b/src/brainyData.ts @@ -6414,35 +6414,8 @@ export class BrainyData implements BrainyDataInterface { * @returns Promise that resolves when environment is loaded */ async loadEnvironment(): Promise { - // Skip in browser environment - Cortex is Node.js only - if (typeof window !== 'undefined' || typeof process === 'undefined') { - prodLog.debug('Cortex not available in browser environment') - return - } - - try { - // Import Cortex dynamically to avoid circular dependencies - const { CortexConfig } = await import('./cortex/config.js') - const cortex = CortexConfig.getInstance() - - // Load using current storage - cortex['brainy'] = this - cortex['config'] = { - storage: this.storage ? { type: 'existing' } : { type: 'memory' }, - environments: { current: process.env.NODE_ENV || 'development' } - } - - // Load all configurations as environment variables - const env = await cortex.loadEnvironment() - - // Apply to process.env - Object.assign(process.env, env) - - prodLog.info(`āœ… Loaded ${Object.keys(env).length} environment variables from Cortex`) - } catch (error) { - // Cortex not configured, skip silently (backwards compatibility) - prodLog.debug('Cortex not configured, skipping environment loading') - } + // Cortex integration coming in next release + prodLog.debug('Cortex integration coming soon') } /** @@ -6452,20 +6425,8 @@ export class BrainyData implements BrainyDataInterface { * @param options Options including encryption */ async setConfig(key: string, value: any, options?: { encrypt?: boolean }): Promise { - // Skip in browser environment - Cortex is Node.js only - if (typeof window !== 'undefined' || typeof process === 'undefined') { - prodLog.warn('Cortex not available in browser environment') - return - } - - try { - const { CortexConfig } = await import('./cortex/config.js') - const cortex = CortexConfig.getInstance() - cortex['brainy'] = this - await cortex.set(key, value, options) - } catch (error) { - prodLog.warn('Cortex not available, config not saved') - } + // Cortex integration coming in next release + prodLog.debug('Cortex integration coming soon') } /** @@ -6474,21 +6435,9 @@ export class BrainyData implements BrainyDataInterface { * @returns Configuration value or undefined */ async getConfig(key: string): Promise { - // Skip in browser environment - Cortex is Node.js only - if (typeof window !== 'undefined' || typeof process === 'undefined') { - prodLog.debug('Cortex not available in browser environment') - return undefined - } - - try { - const { CortexConfig } = await import('./cortex/config.js') - const cortex = CortexConfig.getInstance() - cortex['brainy'] = this - return await cortex.get(key) - } catch (error) { - prodLog.debug('Cortex not available') - return undefined - } + // Cortex integration coming in next release + prodLog.debug('Cortex integration coming soon') + return undefined } /** @@ -6513,7 +6462,7 @@ export class BrainyData implements BrainyDataInterface { } // Store coordination plan in _system directory - await this.addNoun({ + await this.add({ id: '_system/coordination', type: 'cortex_coordination', metadata: coordinationPlan @@ -6529,7 +6478,7 @@ export class BrainyData implements BrainyDataInterface { */ async checkCoordination(): Promise { try { - const coordination = await this.getNoun('_system/coordination') + const coordination = await this.get('_system/coordination') return coordination?.metadata } catch (error) { return null diff --git a/src/cortex/cli.ts b/src/cortex/cli.ts deleted file mode 100644 index 6379f9ae..00000000 --- a/src/cortex/cli.ts +++ /dev/null @@ -1,225 +0,0 @@ -#!/usr/bin/env node - -/** - * Cortex - The Command Center for Brainy - * - * A powerful yet simple CLI for managing Brainy databases, configurations, - * migrations, and distributed coordination. - */ - -import { Command } from 'commander' -import { BrainyData } from '../brainyData.js' -import { CortexConfig } from './config.js' -import { CortexCommands } from './commands/index.js' -import { version } from '../../package.json' assert { type: 'json' } -import chalk from 'chalk' -import ora from 'ora' - -const program = new Command() - -program - .name('cortex') - .description(chalk.cyan('🧠 Cortex - Command Center for Brainy')) - .version(version) - -// Initialize command -program - .command('init') - .description('Initialize Cortex in your project') - .option('-i, --interactive', 'Interactive setup mode', true) - .option('-s, --storage ', 'Storage type (s3, filesystem, memory, opfs)') - .action(async (options) => { - const spinner = ora('Initializing Cortex...').start() - try { - await CortexCommands.init(options) - spinner.succeed(chalk.green('āœ… Cortex initialized successfully!')) - } catch (error) { - spinner.fail(chalk.red(`Failed to initialize: ${error.message}`)) - process.exit(1) - } - }) - -// Config management -const config = program.command('config') -config.description('Manage configuration and secrets') - -config - .command('set [value]') - .description('Set a configuration value') - .option('-e, --encrypt', 'Encrypt the value', true) - .option('--env', 'Set as environment variable') - .action(async (key, value, options) => { - await CortexCommands.configSet(key, value, options) - }) - -config - .command('get ') - .description('Get a configuration value') - .action(async (key) => { - await CortexCommands.configGet(key) - }) - -config - .command('list') - .description('List all configuration keys') - .option('--show-values', 'Show decrypted values') - .action(async (options) => { - await CortexCommands.configList(options) - }) - -config - .command('import ') - .description('Import configuration from .env file') - .action(async (file) => { - await CortexCommands.configImport(file) - }) - -// Environment loading -program - .command('env') - .description('Load environment variables from Cortex') - .option('--export', 'Export as shell commands') - .action(async (options) => { - await CortexCommands.envLoad(options) - }) - -// Migration commands -program - .command('migrate') - .description('Migrate to new storage') - .requiredOption('--to ', 'Target storage URL (e.g., s3://new-bucket)') - .option('--strategy ', 'Migration strategy (immediate, gradual, test)', 'gradual') - .option('--dry-run', 'Test migration without making changes') - .action(async (options) => { - const spinner = ora('Planning migration...').start() - try { - await CortexCommands.migrate(options) - spinner.succeed(chalk.green('āœ… Migration completed successfully!')) - } catch (error) { - spinner.fail(chalk.red(`Migration failed: ${error.message}`)) - process.exit(1) - } - }) - -// Sync command -program - .command('sync') - .description('Synchronize all connected services') - .option('--force', 'Force synchronization') - .action(async (options) => { - await CortexCommands.sync(options) - }) - -// Query command -program - .command('query ') - .description('Query data from Brainy with advanced filtering') - .option('-t, --type ', 'Filter by type (noun, verb)') - .option('-f, --filter ', 'MongoDB-style filter (e.g., {"age": {"$gte": 18}})') - .option('-m, --metadata ', 'Metadata filter (alias for --filter)') - .option('-l, --limit ', 'Limit results', '10') - .option('--json', 'Output as JSON') - .option('--explain', 'Show query execution plan') - .action(async (pattern, options) => { - await CortexCommands.query(pattern, options) - }) - -// Stats command -program - .command('stats') - .description('Show database statistics') - .option('--json', 'Output as JSON') - .action(async (options) => { - await CortexCommands.stats(options) - }) - -// Backup command -program - .command('backup [destination]') - .description('Backup Brainy data') - .option('--include-config', 'Include configuration', true) - .option('--compress', 'Compress backup', true) - .action(async (destination, options) => { - const spinner = ora('Creating backup...').start() - try { - const backupPath = await CortexCommands.backup(destination, options) - spinner.succeed(chalk.green(`āœ… Backup created: ${backupPath}`)) - } catch (error) { - spinner.fail(chalk.red(`Backup failed: ${error.message}`)) - process.exit(1) - } - }) - -// Restore command -program - .command('restore ') - .description('Restore from backup') - .option('--force', 'Overwrite existing data') - .action(async (source, options) => { - const spinner = ora('Restoring from backup...').start() - try { - await CortexCommands.restore(source, options) - spinner.succeed(chalk.green('āœ… Restore completed successfully!')) - } catch (error) { - spinner.fail(chalk.red(`Restore failed: ${error.message}`)) - process.exit(1) - } - }) - -// Inspect command -program - .command('inspect ') - .description('Inspect a specific noun or verb') - .option('--raw', 'Show raw data') - .action(async (id, options) => { - await CortexCommands.inspect(id, options) - }) - -// Reindex command -program - .command('reindex') - .description('Rebuild all indexes') - .option('--type ', 'Index type to rebuild (metadata, hnsw, all)', 'all') - .action(async (options) => { - const spinner = ora('Rebuilding indexes...').start() - try { - await CortexCommands.reindex(options) - spinner.succeed(chalk.green('āœ… Indexes rebuilt successfully!')) - } catch (error) { - spinner.fail(chalk.red(`Reindex failed: ${error.message}`)) - process.exit(1) - } - }) - -// Health check -program - .command('health') - .description('Check system health') - .option('--verbose', 'Show detailed health information') - .action(async (options) => { - await CortexCommands.health(options) - }) - -// Interactive shell -program - .command('shell') - .description('Start interactive Cortex shell') - .action(async () => { - await CortexCommands.shell() - }) - -// Status command -program - .command('status') - .description('Show Cortex and cluster status') - .action(async () => { - await CortexCommands.status() - }) - -// Parse command line arguments -program.parse(process.argv) - -// Show help if no command provided -if (!process.argv.slice(2).length) { - program.outputHelp() -} \ No newline at end of file diff --git a/src/cortex/commands/index.ts b/src/cortex/commands/index.ts deleted file mode 100644 index d625ac2c..00000000 --- a/src/cortex/commands/index.ts +++ /dev/null @@ -1,713 +0,0 @@ -/** - * Cortex Commands Implementation - * - * All CLI commands for managing Brainy through Cortex - */ - -import { CortexConfig } from '../config.js' -import { BrainyData } from '../../brainyData.js' -import * as fs from 'fs/promises' -import * as path from 'path' -import * as readline from 'readline/promises' -import { stdin as input, stdout as output } from 'process' -import chalk from 'chalk' -import Table from 'cli-table3' - -export class CortexCommands { - /** - * Initialize Cortex in a project - */ - static async init(options: any): Promise { - const config = CortexConfig.getInstance() - - if (options.interactive) { - const rl = readline.createInterface({ input, output }) - - console.log(chalk.cyan('\n🧠 Welcome to Cortex - Brainy Command Center\n')) - - // Storage type - const storageType = await rl.question( - 'Storage type (s3/filesystem/memory/opfs) [s3]: ' - ) || 's3' - - let storageConfig: any = { type: storageType } - - // Storage-specific configuration - if (storageType === 's3') { - storageConfig.bucket = await rl.question('S3 bucket name: ') - storageConfig.region = await rl.question('AWS region [us-east-1]: ') || 'us-east-1' - - const useEnvCreds = await rl.question( - 'Use AWS credentials from environment? (y/n) [y]: ' - ) || 'y' - - if (useEnvCreds !== 'y') { - storageConfig.accessKeyId = await rl.question('AWS Access Key ID: ') - storageConfig.secretAccessKey = await rl.question('AWS Secret Access Key: ') - } - } else if (storageType === 'filesystem') { - storageConfig.path = await rl.question( - `Storage path [${path.join(process.cwd(), '.brainy-data')}]: ` - ) || path.join(process.cwd(), '.brainy-data') - } - - // Encryption - const enableEncryption = await rl.question( - 'Enable encrypted configuration? (y/n) [y]: ' - ) || 'y' - - // Coordination - const enableCoordination = await rl.question( - 'Enable distributed coordination? (y/n) [y]: ' - ) || 'y' - - rl.close() - - await config.init({ - storage: storageConfig, - encryption: { - enabled: enableEncryption === 'y', - keyDerivation: 'pbkdf2' - }, - coordination: { - enabled: enableCoordination === 'y', - realtime: false, - pollInterval: 30000 - } - }) - - console.log(chalk.green('\nāœ… Cortex initialized successfully!')) - console.log(chalk.gray('Configuration saved to .brainy/cortex.json')) - console.log(chalk.gray('Master key saved to .brainy/cortex.key')) - console.log(chalk.yellow('\nāš ļø Keep your master key safe!')) - - } else { - // Non-interactive mode - await config.init({ - storage: options.storage ? { type: options.storage } : undefined - }) - } - } - - /** - * Set a configuration value - */ - static async configSet(key: string, value: string | undefined, options: any): Promise { - const config = CortexConfig.getInstance() - await config.load() - - // If no value provided, prompt for it - if (!value) { - const rl = readline.createInterface({ input, output }) - value = await rl.question(`Enter value for ${key}: `) - rl.close() - } - - await config.set(key, value, { encrypt: options.encrypt }) - console.log(chalk.green(`āœ… Set ${key}`)) - } - - /** - * Get a configuration value - */ - static async configGet(key: string): Promise { - const config = CortexConfig.getInstance() - await config.load() - - const value = await config.get(key) - - if (value === undefined) { - console.log(chalk.yellow(`Configuration key '${key}' not found`)) - } else { - console.log(value) - } - } - - /** - * List all configuration keys - */ - static async configList(options: any): Promise { - const config = CortexConfig.getInstance() - await config.load() - - const configs = await config.list() - - if (configs.length === 0) { - console.log(chalk.yellow('No configurations found')) - return - } - - const table = new Table({ - head: ['Key', 'Encrypted', 'Environment'], - style: { head: ['cyan'] } - }) - - for (const cfg of configs) { - const row = [ - cfg.key, - cfg.encrypted ? chalk.green('āœ“') : chalk.gray('-'), - cfg.environment - ] - - if (options.showValues) { - const value = await config.get(cfg.key) - row.push(value ? String(value).substring(0, 50) : '') - } - - table.push(row) - } - - console.log(table.toString()) - } - - /** - * Import configuration from .env file - */ - static async configImport(file: string): Promise { - const config = CortexConfig.getInstance() - await config.load() - - await config.importEnv(file) - console.log(chalk.green(`āœ… Imported configuration from ${file}`)) - } - - /** - * Load environment variables - */ - static async envLoad(options: any): Promise { - const config = CortexConfig.getInstance() - await config.load() - - const env = await config.loadEnvironment() - - if (options.export) { - // Output as shell export commands - for (const [key, value] of Object.entries(env)) { - console.log(`export ${key}="${value}"`) - } - } else { - // Set in current process - Object.assign(process.env, env) - console.log(chalk.green(`āœ… Loaded ${Object.keys(env).length} environment variables`)) - } - } - - /** - * Migrate to new storage - */ - static async migrate(options: any): Promise { - const config = CortexConfig.getInstance() - await config.load() - - const brainy = config.getBrainy() - - // Parse target storage URL - const targetUrl = new URL(options.to) - let targetConfig: any = {} - - if (targetUrl.protocol === 's3:') { - targetConfig = { - type: 's3', - bucket: targetUrl.hostname, - region: targetUrl.searchParams.get('region') || 'us-east-1' - } - } else if (targetUrl.protocol === 'file:') { - targetConfig = { - type: 'filesystem', - path: targetUrl.pathname - } - } - - // Create coordination plan - const coordinationPlan = { - version: 1, - timestamp: new Date().toISOString(), - migration: { - enabled: true, - target: targetConfig, - strategy: options.strategy, - dryRun: options.dryRun || false - } - } - - // Store coordination plan in current storage - await brainy.addNoun({ - id: '_system/coordination', - type: 'cortex_coordination', - metadata: coordinationPlan - }) - - console.log(chalk.cyan('šŸ“‹ Migration plan created:')) - console.log(JSON.stringify(coordinationPlan, null, 2)) - - if (!options.dryRun) { - console.log(chalk.yellow('\nā³ Migration will begin shortly...')) - console.log(chalk.gray('All services will automatically detect and execute the migration')) - } - } - - /** - * Synchronize services - */ - static async sync(options: any): Promise { - const config = CortexConfig.getInstance() - await config.load() - - const brainy = config.getBrainy() - - // Trigger sync by updating coordination timestamp - await brainy.addNoun({ - id: '_system/sync', - type: 'cortex_sync', - metadata: { - timestamp: new Date().toISOString(), - force: options.force || false - } - }) - - console.log(chalk.green('āœ… Sync signal sent to all services')) - } - - /** - * Query data with advanced filtering - */ - static async query(pattern: string, options: any): Promise { - const config = CortexConfig.getInstance() - await config.load() - - const brainy = config.getBrainy() - - // Parse metadata filter if provided - let metadataFilter = null - if (options.filter || options.metadata) { - try { - metadataFilter = JSON.parse(options.filter || options.metadata) - } catch (error) { - console.log(chalk.red(`Invalid filter JSON: ${error.message}`)) - return - } - } - - // Show query plan if requested - if (options.explain) { - console.log(chalk.cyan('\nšŸ“‹ Query Plan:')) - console.log(`Pattern: "${pattern}"`) - if (metadataFilter) { - console.log(`Filter: ${JSON.stringify(metadataFilter, null, 2)}`) - } - console.log(`Limit: ${options.limit || 10}\n`) - } - - // Perform search with metadata filtering - const results = await brainy.searchNouns(pattern, { - limit: parseInt(options.limit) || 10, - metadata: metadataFilter - }) - - if (options.json) { - console.log(JSON.stringify(results, null, 2)) - } else { - if (results.length === 0) { - console.log(chalk.yellow('No results found')) - return - } - - const table = new Table({ - head: ['ID', 'Type', 'Score', 'Sample Metadata'], - style: { head: ['cyan'] }, - colWidths: [30, 15, 10, 40] - }) - - for (const result of results) { - // Show a sample of metadata - let metaSample = '' - if (result.metadata) { - const keys = Object.keys(result.metadata).slice(0, 3) - metaSample = keys.map(k => `${k}: ${JSON.stringify(result.metadata[k])}`).join(', ') - if (Object.keys(result.metadata).length > 3) { - metaSample += ', ...' - } - } - - table.push([ - result.id.substring(0, 28), - result.type || '-', - result.score?.toFixed(3) || '-', - metaSample.substring(0, 38) - ]) - } - - console.log(table.toString()) - console.log(chalk.gray(`\nFound ${results.length} results`)) - } - } - - /** - * Show statistics - */ - static async stats(options: any): Promise { - const config = CortexConfig.getInstance() - await config.load() - - const brainy = config.getBrainy() - const stats = await brainy.getStatistics() - - if (options.json) { - console.log(JSON.stringify(stats, null, 2)) - } else { - console.log(chalk.cyan('\nšŸ“Š Brainy Statistics\n')) - console.log(`Nouns: ${chalk.green(stats.totalNouns)}`) - console.log(`Verbs: ${chalk.green(stats.totalVerbs)}`) - console.log(`Storage Used: ${chalk.green(this.formatBytes(stats.storageUsed || 0))}`) - console.log(`Index Size: ${chalk.green(this.formatBytes(stats.indexSize || 0))}`) - - if (stats.metadata) { - console.log(`\nMetadata:`) - for (const [key, value] of Object.entries(stats.metadata)) { - console.log(` ${key}: ${chalk.gray(JSON.stringify(value))}`) - } - } - } - } - - /** - * Create backup - */ - static async backup(destination: string | undefined, options: any): Promise { - const config = CortexConfig.getInstance() - await config.load() - - const timestamp = new Date().toISOString().replace(/[:.]/g, '-') - const backupName = `brainy-backup-${timestamp}` - const backupPath = destination || path.join(process.cwd(), `${backupName}.json`) - - const brainy = config.getBrainy() - - // Export all data - const backup: any = { - version: 1, - timestamp: new Date().toISOString(), - data: { - nouns: [], - verbs: [] - } - } - - // Export nouns with pagination - let offset = 0 - let hasMore = true - while (hasMore) { - const result = await brainy.getNouns({ - pagination: { offset, limit: 100 } - }) - backup.data.nouns.push(...result.items) - hasMore = result.hasMore - offset += 100 - } - - // Export verbs with pagination - offset = 0 - hasMore = true - while (hasMore) { - const result = await brainy.getVerbs({ - pagination: { offset, limit: 100 } - }) - backup.data.verbs.push(...result.items) - hasMore = result.hasMore - offset += 100 - } - - // Include configuration if requested - if (options.includeConfig) { - backup.config = await config.list() - } - - // Save backup - const backupContent = JSON.stringify(backup, null, 2) - - if (options.compress) { - const zlib = await import('zlib') - const compressed = await new Promise((resolve, reject) => { - zlib.gzip(Buffer.from(backupContent), (err, result) => { - if (err) reject(err) - else resolve(result) - }) - }) - await fs.writeFile(`${backupPath}.gz`, compressed) - return `${backupPath}.gz` - } else { - await fs.writeFile(backupPath, backupContent) - return backupPath - } - } - - /** - * Restore from backup - */ - static async restore(source: string, options: any): Promise { - const config = CortexConfig.getInstance() - await config.load() - - let backupContent: string - - if (source.endsWith('.gz')) { - const zlib = await import('zlib') - const compressed = await fs.readFile(source) - backupContent = await new Promise((resolve, reject) => { - zlib.gunzip(compressed, (err, result) => { - if (err) reject(err) - else resolve(result.toString()) - }) - }) - } else { - backupContent = await fs.readFile(source, 'utf-8') - } - - const backup = JSON.parse(backupContent) - const brainy = config.getBrainy() - - if (!options.force) { - const rl = readline.createInterface({ input, output }) - const confirm = await rl.question( - chalk.yellow('āš ļø This will overwrite existing data. Continue? (y/n): ') - ) - rl.close() - - if (confirm !== 'y') { - console.log('Restore cancelled') - return - } - } - - // Restore nouns - for (const noun of backup.data.nouns) { - await brainy.addNoun(noun) - } - - // Restore verbs - for (const verb of backup.data.verbs) { - await brainy.addVerb(verb) - } - - console.log(chalk.green(`āœ… Restored ${backup.data.nouns.length} nouns and ${backup.data.verbs.length} verbs`)) - } - - /** - * Inspect an item - */ - static async inspect(id: string, options: any): Promise { - const config = CortexConfig.getInstance() - await config.load() - - const brainy = config.getBrainy() - - try { - // Try as noun first - const noun = await brainy.getNoun(id) - if (noun) { - if (options.raw) { - console.log(JSON.stringify(noun, null, 2)) - } else { - console.log(chalk.cyan(`\nšŸ“¦ Noun: ${id}\n`)) - console.log(`Type: ${chalk.green(noun.type || 'unknown')}`) - if (noun.metadata) { - console.log(`Metadata:`) - console.log(JSON.stringify(noun.metadata, null, 2)) - } - } - return - } - } catch (e) { - // Not a noun, try verb - } - - try { - // Try as verb - const verb = await brainy.getVerb(id) - if (verb) { - if (options.raw) { - console.log(JSON.stringify(verb, null, 2)) - } else { - console.log(chalk.cyan(`\nšŸ”— Verb: ${id}\n`)) - console.log(`Type: ${chalk.green(verb.type)}`) - console.log(`Source: ${chalk.blue(verb.source)}`) - console.log(`Target: ${chalk.blue(verb.target)}`) - if (verb.metadata) { - console.log(`Metadata:`) - console.log(JSON.stringify(verb.metadata, null, 2)) - } - } - return - } - } catch (e) { - // Not found - } - - console.log(chalk.red(`Item '${id}' not found`)) - } - - /** - * Reindex database - */ - static async reindex(options: any): Promise { - const config = CortexConfig.getInstance() - await config.load() - - const brainy = config.getBrainy() - - if (options.type === 'metadata' || options.type === 'all') { - await brainy.rebuildMetadataIndex() - } - - if (options.type === 'hnsw' || options.type === 'all') { - // HNSW reindex would go here - console.log(chalk.yellow('HNSW reindexing not yet implemented')) - } - } - - /** - * Health check - */ - static async health(options: any): Promise { - const config = CortexConfig.getInstance() - - try { - await config.load() - const brainy = config.getBrainy() - const stats = await brainy.getStatistics() - - console.log(chalk.green('āœ… Cortex: Healthy')) - console.log(chalk.green('āœ… Brainy: Connected')) - console.log(chalk.green(`āœ… Storage: Active (${stats.totalNouns} nouns, ${stats.totalVerbs} verbs)`)) - - if (options.verbose) { - console.log('\nDetailed Health:') - console.log(`- Config Path: ${path.join(process.cwd(), '.brainy/cortex.json')}`) - console.log(`- Storage Type: ${config.getStorageConfig()?.type}`) - console.log(`- Environment: ${config.getCurrentEnvironment()}`) - } - } catch (error: any) { - console.log(chalk.red('āŒ Cortex: Error')) - console.log(chalk.red(` ${error.message}`)) - process.exit(1) - } - } - - /** - * Interactive shell - */ - static async shell(): Promise { - const config = CortexConfig.getInstance() - await config.load() - - console.log(chalk.cyan('\n🧠 Cortex Interactive Shell\n')) - console.log(chalk.gray('Type "help" for commands, "exit" to quit\n')) - - const rl = readline.createInterface({ input, output }) - - while (true) { - const command = await rl.question(chalk.cyan('cortex> ')) - - if (command === 'exit' || command === 'quit') { - break - } - - if (command === 'help') { - console.log(` -Available commands: - query - Search for data - get - Get config value - set - Set config value - stats - Show statistics - health - Check health - exit - Exit shell - `) - continue - } - - // Parse and execute command - const [cmd, ...args] = command.split(' ') - - try { - switch (cmd) { - case 'query': - await this.query(args.join(' '), { limit: 5 }) - break - case 'get': - await this.configGet(args[0]) - break - case 'set': - await this.configSet(args[0], args.slice(1).join(' '), { encrypt: true }) - break - case 'stats': - await this.stats({}) - break - case 'health': - await this.health({}) - break - default: - console.log(chalk.yellow(`Unknown command: ${cmd}`)) - } - } catch (error: any) { - console.log(chalk.red(`Error: ${error.message}`)) - } - } - - rl.close() - console.log(chalk.gray('\nGoodbye!\n')) - } - - /** - * Show status - */ - static async status(): Promise { - const config = CortexConfig.getInstance() - - try { - await config.load() - const brainy = config.getBrainy() - const stats = await brainy.getStatistics() - - console.log(chalk.cyan('\n🧠 Cortex Status\n')) - - const table = new Table({ - style: { head: ['cyan'] } - }) - - table.push( - { 'Configuration': chalk.green('āœ“ Loaded') }, - { 'Storage': `${config.getStorageConfig()?.type}` }, - { 'Environment': config.getCurrentEnvironment() }, - { 'Encryption': chalk.green('āœ“ Enabled') }, - { 'Total Nouns': stats.totalNouns.toString() }, - { 'Total Verbs': stats.totalVerbs.toString() }, - { 'Storage Used': this.formatBytes(stats.storageUsed || 0) } - ) - - console.log(table.toString()) - - // Check for active migrations - const coordinationNoun = await brainy.getNoun('_system/coordination') - if (coordinationNoun?.metadata?.migration?.enabled) { - console.log(chalk.yellow('\nāš ļø Active Migration:')) - console.log(` Target: ${coordinationNoun.metadata.migration.target.type}`) - console.log(` Strategy: ${coordinationNoun.metadata.migration.strategy}`) - } - - } catch (error: any) { - console.log(chalk.red('āŒ Cortex not initialized')) - console.log(chalk.gray('Run "cortex init" to get started')) - } - } - - /** - * Format bytes to human readable - */ - private static formatBytes(bytes: number): string { - if (bytes === 0) return '0 B' - const k = 1024 - const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] - const i = Math.floor(Math.log(bytes) / Math.log(k)) - return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}` - } -} \ No newline at end of file diff --git a/src/cortex/config.ts b/src/cortex/config.ts deleted file mode 100644 index 0c36d6aa..00000000 --- a/src/cortex/config.ts +++ /dev/null @@ -1,410 +0,0 @@ -/** - * Cortex Configuration Management - * - * Handles encrypted configuration storage, environment loading, - * and distributed coordination through Brainy. - */ - -import { BrainyData } from '../brainyData.js' -import { StorageConfig } from '../coreTypes.js' -import * as crypto from 'crypto' -import * as fs from 'fs/promises' -import * as path from 'path' -import { fileURLToPath } from 'url' -import { dirname } from 'path' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = dirname(__filename) - -export interface CortexConfigData { - version: number - storage: StorageConfig - encryption?: { - enabled: boolean - keyDerivation: 'pbkdf2' | 'scrypt' - iterations?: number - } - coordination?: { - enabled: boolean - realtime: boolean - pollInterval: number - } - environments?: { - current: string - available: string[] - } -} - -export interface EncryptedValue { - encrypted: true - algorithm: string - iv: string - authTag: string - data: string -} - -export class CortexConfig { - private static instance: CortexConfig - private brainy?: BrainyData - private config?: CortexConfigData - private configPath: string - private masterKey?: Buffer - - private constructor() { - this.configPath = path.join(process.cwd(), '.brainy', 'cortex.json') - } - - static getInstance(): CortexConfig { - if (!CortexConfig.instance) { - CortexConfig.instance = new CortexConfig() - } - return CortexConfig.instance - } - - /** - * Initialize Cortex configuration - */ - async init(options: Partial = {}): Promise { - // Create .brainy directory if it doesn't exist - const brainyDir = path.dirname(this.configPath) - await fs.mkdir(brainyDir, { recursive: true }) - - // Default configuration - const defaultConfig: CortexConfigData = { - version: 1, - storage: options.storage || { type: 'memory' }, - encryption: { - enabled: true, - keyDerivation: 'pbkdf2', - iterations: 100000 - }, - coordination: { - enabled: true, - realtime: false, - pollInterval: 30000 // 30 seconds - }, - environments: { - current: process.env.NODE_ENV || 'development', - available: ['development', 'staging', 'production'] - } - } - - this.config = { ...defaultConfig, ...options } - - // Save configuration - await this.saveConfig() - - // Initialize Brainy with the configuration - await this.initBrainy() - - // Generate or load master key - await this.initMasterKey() - } - - /** - * Load existing configuration - */ - async load(): Promise { - try { - const configData = await fs.readFile(this.configPath, 'utf-8') - this.config = JSON.parse(configData) - await this.initBrainy() - await this.initMasterKey() - } catch (error) { - throw new Error(`No Cortex configuration found. Run 'cortex init' first.`) - } - } - - /** - * Save configuration to disk - */ - private async saveConfig(): Promise { - await fs.writeFile( - this.configPath, - JSON.stringify(this.config, null, 2), - 'utf-8' - ) - } - - /** - * Initialize Brainy instance - */ - private async initBrainy(): Promise { - if (!this.config) { - throw new Error('Configuration not loaded') - } - - this.brainy = new BrainyData({ - storage: this.config.storage, - writeOnlyMode: false, - enableMetadataIndexing: true - }) - - await this.brainy.init() - } - - /** - * Initialize or load master encryption key - */ - private async initMasterKey(): Promise { - const keyPath = path.join(process.cwd(), '.brainy', 'cortex.key') - - // Try to load from environment first - if (process.env.CORTEX_MASTER_KEY) { - this.masterKey = Buffer.from(process.env.CORTEX_MASTER_KEY, 'base64') - return - } - - // Try to load from file - try { - const keyData = await fs.readFile(keyPath, 'utf-8') - this.masterKey = Buffer.from(keyData, 'base64') - } catch (error) { - // Generate new key - this.masterKey = crypto.randomBytes(32) - await fs.writeFile(keyPath, this.masterKey.toString('base64'), 'utf-8') - - // Set restrictive permissions (Unix-like systems) - try { - await fs.chmod(keyPath, 0o600) - } catch (e) { - // Windows doesn't support chmod, ignore - } - - console.log('šŸ” Generated new master key at .brainy/cortex.key') - console.log('āš ļø Keep this key safe! You\'ll need it to decrypt your configs.') - } - } - - /** - * Get Brainy instance - */ - getBrainy(): BrainyData { - if (!this.brainy) { - throw new Error('Brainy not initialized. Run load() first.') - } - return this.brainy - } - - /** - * Encrypt a value - */ - encrypt(value: string): EncryptedValue { - if (!this.masterKey) { - throw new Error('Master key not initialized') - } - - const algorithm = 'aes-256-gcm' - const iv = crypto.randomBytes(16) - const cipher = crypto.createCipheriv(algorithm, this.masterKey, iv) - - let encrypted = cipher.update(value, 'utf8', 'hex') - encrypted += cipher.final('hex') - - const authTag = cipher.getAuthTag() - - return { - encrypted: true, - algorithm, - iv: iv.toString('hex'), - authTag: authTag.toString('hex'), - data: encrypted - } - } - - /** - * Decrypt a value - */ - decrypt(encryptedValue: EncryptedValue): string { - if (!this.masterKey) { - throw new Error('Master key not initialized') - } - - const decipher = crypto.createDecipheriv( - encryptedValue.algorithm, - this.masterKey, - Buffer.from(encryptedValue.iv, 'hex') - ) - - decipher.setAuthTag(Buffer.from(encryptedValue.authTag, 'hex')) - - let decrypted = decipher.update(encryptedValue.data, 'hex', 'utf8') - decrypted += decipher.final('utf8') - - return decrypted - } - - /** - * Set a configuration value in Brainy - */ - async set(key: string, value: any, options: { encrypt?: boolean } = {}): Promise { - if (!this.brainy) { - await this.load() - } - - const configKey = `_cortex/config/${key}` - const configValue = options.encrypt && typeof value === 'string' - ? this.encrypt(value) - : value - - await this.brainy!.addNoun({ - id: configKey, - type: 'cortex_config', - metadata: { - key, - value: configValue, - encrypted: options.encrypt || false, - environment: this.config?.environments?.current, - updatedAt: new Date().toISOString() - } - }) - } - - /** - * Get a configuration value from Brainy - */ - async get(key: string): Promise { - if (!this.brainy) { - await this.load() - } - - const configKey = `_cortex/config/${key}` - - try { - const noun = await this.brainy!.getNoun(configKey) - if (!noun?.metadata?.value) { - return undefined - } - - const value = noun.metadata.value - - // Decrypt if needed - if (value.encrypted === true) { - return this.decrypt(value as EncryptedValue) - } - - return value - } catch (error) { - return undefined - } - } - - /** - * List all configuration keys - */ - async list(): Promise> { - if (!this.brainy) { - await this.load() - } - - const result = await this.brainy!.getNouns({ - filter: { type: 'cortex_config' }, - pagination: { limit: 1000 } - }) - - return result.items.map(noun => ({ - key: noun.metadata?.key || noun.id.replace('_cortex/config/', ''), - encrypted: noun.metadata?.encrypted || false, - environment: noun.metadata?.environment || 'default' - })) - } - - /** - * Load all configurations as environment variables - */ - async loadEnvironment(): Promise> { - if (!this.brainy) { - await this.load() - } - - const configs = await this.brainy!.getNouns({ - filter: { - type: 'cortex_config', - 'metadata.environment': this.config?.environments?.current - }, - pagination: { limit: 1000 } - }) - - const env: Record = {} - - for (const config of configs.items) { - const key = config.metadata?.key || config.id.replace('_cortex/config/', '') - let value = config.metadata?.value - - // Decrypt if needed - if (value?.encrypted === true) { - value = this.decrypt(value as EncryptedValue) - } - - // Convert to string if needed - if (typeof value !== 'string') { - value = JSON.stringify(value) - } - - // Use the key as-is (could be nested like 'database.url') - // Or convert to UPPER_SNAKE_CASE - const envKey = key.toUpperCase().replace(/\./g, '_').replace(/-/g, '_') - env[envKey] = value - } - - return env - } - - /** - * Import configuration from .env file - */ - async importEnv(filePath: string): Promise { - const content = await fs.readFile(filePath, 'utf-8') - const lines = content.split('\n') - - for (const line of lines) { - // Skip comments and empty lines - if (!line.trim() || line.startsWith('#')) { - continue - } - - const [key, ...valueParts] = line.split('=') - const value = valueParts.join('=').trim() - - if (key && value) { - // Detect if it looks like a secret - const isSecret = key.includes('KEY') || - key.includes('SECRET') || - key.includes('PASSWORD') || - key.includes('TOKEN') - - await this.set(key.trim(), value, { encrypt: isSecret }) - } - } - } - - /** - * Get storage configuration - */ - getStorageConfig(): StorageConfig | undefined { - return this.config?.storage - } - - /** - * Get current environment - */ - getCurrentEnvironment(): string { - return this.config?.environments?.current || 'development' - } - - /** - * Switch environment - */ - async switchEnvironment(environment: string): Promise { - if (!this.config) { - await this.load() - } - - if (!this.config!.environments?.available.includes(environment)) { - throw new Error(`Unknown environment: ${environment}`) - } - - this.config!.environments!.current = environment - await this.saveConfig() - } -} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 15ac68ea..10672725 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "moduleResolution": "NodeNext", "esModuleInterop": true, "strict": true, + "resolveJsonModule": true, "declaration": true, "outDir": "./dist", "rootDir": "./src",