From bc5aa37dd0bb5af68391add55c4d9f403f046a83 Mon Sep 17 00:00:00 2001 From: David Snelling Date: Mon, 29 Sep 2025 15:58:25 -0700 Subject: [PATCH] fix: CLI syntax error and add conversation remove command - Fixed bin/brainy.js to import compiled TypeScript CLI - Added conversation remove command to clean up MCP setup - Fixed tsconfig.json to include CLI in compilation - Fixed package.json import in CLI using readFileSync - Fixed storage config structure in conversation setup Fixes the 'Unexpected identifier as' syntax error when running brainy conversation setup globally. Version: 3.19.1 --- CHANGELOG.md | 2 + bin/brainy.js | 2344 +----------------------------- package-lock.json | 4 +- package.json | 2 +- src/cli/commands/conversation.ts | 107 +- src/cli/index.ts | 8 +- tsconfig.json | 4 +- 7 files changed, 126 insertions(+), 2345 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 611dff38..467868d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [3.19.1](https://github.com/soulcraftlabs/brainy/compare/v3.19.0...v3.19.1) (2025-09-29) + ## [3.19.0](https://github.com/soulcraftlabs/brainy/compare/v3.18.0...v3.19.0) (2025-09-29) ## [3.17.0](https://github.com/soulcraftlabs/brainy/compare/v3.16.0...v3.17.0) (2025-09-27) diff --git a/bin/brainy.js b/bin/brainy.js index 19b85183..65d86a51 100755 --- a/bin/brainy.js +++ b/bin/brainy.js @@ -1,2340 +1,14 @@ #!/usr/bin/env node /** - * Brainy CLI - Cleaned Up & Beautiful - * πŸ§ βš›οΈ ONE way to do everything - * - * After the Great Cleanup of 2025: - * - 5 commands total (was 40+) - * - Clear, obvious naming - * - Interactive mode for beginners + * Brainy CLI Wrapper + * + * Imports the compiled TypeScript CLI from dist/cli/index.js + * This ensures TypeScript features work correctly */ -// @ts-ignore -import { program } from 'commander' -import { BrainyData } from '../dist/brainyData.js' -// @ts-ignore -import chalk from 'chalk' -import { readFileSync } from 'fs' -import { dirname, join } from 'path' -import { fileURLToPath } from 'url' -import { createInterface } from 'readline' -// @ts-ignore -import Table from 'cli-table3' - -const __dirname = dirname(fileURLToPath(import.meta.url)) -const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8')) - -// Create single BrainyData instance (the ONE data orchestrator) -let brainy = null -const getBrainy = async () => { - if (!brainy) { - brainy = new BrainyData() - await brainy.init() - } - return brainy -} - -// Beautiful colors matching brainy.png logo -const colors = { - primary: chalk.hex('#3A5F4A'), // Teal container (from logo) - success: chalk.hex('#2D4A3A'), // Deep teal frame (from logo) - info: chalk.hex('#4A6B5A'), // Medium teal - warning: chalk.hex('#D67441'), // Orange (from logo) - error: chalk.hex('#B85C35'), // Deep orange - brain: chalk.hex('#D67441'), // Brain orange (from logo) - cream: chalk.hex('#F5E6A3'), // Cream background (from logo) - dim: chalk.dim, - blue: chalk.blue, - green: chalk.green, - yellow: chalk.yellow, - cyan: chalk.cyan -} - -// Helper functions -const exitProcess = (code = 0) => { - setTimeout(() => process.exit(code), 100) -} - -// Initialize Brainy instance -const initBrainy = async () => { - return new BrainyData() -} - -/** - * Enhanced result formatting using display augmentation - * @param {any} result - The result object from search/get/find - * @param {number} index - Result index for numbering - * @returns {Promise} Formatted result string - */ -const formatResultWithDisplay = async (result, index) => { - try { - // Check if result has display capabilities (enhanced by display augmentation) - if (result.getDisplay && typeof result.getDisplay === 'function') { - const displayFields = await result.getDisplay() - - // Format with enhanced display fields (clean, no icons) - let output = colors.primary(`\n${index + 1}. ${displayFields.title}`) - - if (displayFields.type) { - output += colors.dim(` (${displayFields.type})`) - } - - if (result.score) { - output += colors.info(`\n 🎯 Relevance: ${(result.score * 100).toFixed(1)}%`) - } - - if (result.fusionScore) { - output += colors.info(`\n 🧠 AI Score: ${(result.fusionScore * 100).toFixed(1)}%`) - } - - if (displayFields.description && displayFields.description !== displayFields.title) { - output += colors.info(`\n πŸ“„ ${displayFields.description}`) - } - - if (displayFields.tags && displayFields.tags.length > 0) { - output += colors.cyan(`\n 🏷️ ${displayFields.tags.join(', ')}`) - } - - // Show relationship info for verbs - if (displayFields.relationship) { - output += colors.yellow(`\n πŸ”— ${displayFields.relationship}`) - } - - // Show metadata only if there's additional useful info - if (result.metadata && Object.keys(result.metadata).length > 0) { - const filteredMetadata = Object.fromEntries( - Object.entries(result.metadata).filter(([key]) => - !key.startsWith('_') && !['type', 'title', 'description', 'icon'].includes(key) - ) - ) - if (Object.keys(filteredMetadata).length > 0) { - output += colors.dim(`\n πŸ“ ${JSON.stringify(filteredMetadata)}`) - } - } - - return output - } - } catch (error) { - // Fallback silently to basic formatting if display augmentation fails - } - - // Fallback: Basic formatting without display augmentation - let output = colors.primary(`\n${index + 1}. ${result.content || result.id}`) - - if (result.score) { - output += colors.info(`\n Relevance: ${(result.score * 100).toFixed(1)}%`) - } - - if (result.fusionScore) { - output += colors.info(`\n AI Score: ${(result.fusionScore * 100).toFixed(1)}%`) - } - - if (result.type) { - output += colors.info(`\n Type: ${result.type}`) - } - - if (result.metadata && Object.keys(result.metadata).length > 0) { - output += colors.dim(`\n Metadata: ${JSON.stringify(result.metadata)}`) - } - - return output -} - -/** - * Enhanced single item formatting for get command - * @param {any} item - The item object - * @param {string} format - Output format (json, table, plain) - * @returns {Promise} Formatted item string - */ -const formatItemWithDisplay = async (item, format = 'plain') => { - if (format === 'json') { - return JSON.stringify(item, null, 2) - } - - try { - // Check if item has display capabilities - if (item.getDisplay && typeof item.getDisplay === 'function') { - const displayFields = await item.getDisplay() - - if (format === 'table') { - const table = new Table({ - head: [colors.brain('Property'), colors.brain('Value')], - style: { head: [], border: [] } - }) - - table.push(['ID', colors.primary(item.id)]) - table.push(['Title', colors.primary(displayFields.title)]) - table.push(['Type', colors.info(displayFields.type)]) - table.push(['Description', colors.info(displayFields.description)]) - - if (displayFields.tags && displayFields.tags.length > 0) { - table.push(['Tags', colors.cyan(displayFields.tags.join(', '))]) - } - - if (displayFields.relationship) { - table.push(['Relationship', colors.yellow(displayFields.relationship)]) - } - - if (item.content && item.content !== displayFields.title) { - table.push(['Content', colors.dim(item.content)]) - } - - // Add non-internal metadata - if (item.metadata) { - Object.entries(item.metadata).forEach(([key, value]) => { - if (!key.startsWith('_') && !['type', 'title', 'description', 'icon'].includes(key)) { - table.push([key, colors.dim(JSON.stringify(value))]) - } - }) - } - - return table.toString() - } else { - // Plain format with display enhancement - let output = colors.primary(`ID: ${item.id}`) - output += colors.primary(`\nTitle: ${displayFields.title}`) - output += colors.info(`\nType: ${displayFields.type}`) - output += colors.info(`\nDescription: ${displayFields.description}`) - - if (displayFields.tags && displayFields.tags.length > 0) { - output += colors.cyan(`\nTags: ${displayFields.tags.join(', ')}`) - } - - if (displayFields.relationship) { - output += colors.yellow(`\nRelationship: ${displayFields.relationship}`) - } - - if (item.content && item.content !== displayFields.title) { - output += colors.dim(`\nOriginal Content: ${item.content}`) - } - - // Show additional metadata - if (item.metadata) { - const additionalMetadata = Object.fromEntries( - Object.entries(item.metadata).filter(([key]) => - !key.startsWith('_') && !['type', 'title', 'description', 'icon'].includes(key) - ) - ) - if (Object.keys(additionalMetadata).length > 0) { - output += colors.dim(`\nAdditional Metadata: ${JSON.stringify(additionalMetadata, null, 2)}`) - } - } - - return output - } - } - } catch (error) { - // Fallback silently to basic formatting - } - - // Fallback: Basic formatting - if (format === 'table') { - const table = new Table({ - head: [colors.brain('Property'), colors.brain('Value')], - style: { head: [], border: [] } - }) - - table.push(['ID', colors.primary(item.id)]) - table.push(['Content', colors.info(item.content || 'N/A')]) - if (item.metadata) { - Object.entries(item.metadata).forEach(([key, value]) => { - table.push([key, colors.dim(JSON.stringify(value))]) - }) - } - return table.toString() - } else { - let output = colors.primary(`ID: ${item.id}`) - if (item.content) { - output += colors.info(`\nContent: ${item.content}`) - } - if (item.metadata && Object.keys(item.metadata).length > 0) { - output += colors.info(`\nMetadata: ${JSON.stringify(item.metadata, null, 2)}`) - } - return output - } -} - -const wrapAction = (fn) => { - return async (...args) => { - try { - await fn(...args) - exitProcess(0) - } catch (error) { - console.error(colors.error('Error:'), error.message) - exitProcess(1) - } - } -} - -// AI Response Generation with multiple model support -async function generateAIResponse(message, brainy, options) { - const model = options.model || 'local' - - // Get relevant context from user's data - const contextResults = await brainy.search(message, { - limit: 5, - includeContent: true, - scoreThreshold: 0.3 - }) - - const context = contextResults.map(r => r.content).join('\n') - const prompt = `Based on the following context from the user's data, answer their question: - -Context: -${context} - -Question: ${message} - -Answer:` - - switch (model) { - case 'local': - case 'ollama': - return await callOllamaModel(prompt, options) - - case 'openai': - case 'gpt-3.5-turbo': - case 'gpt-4': - return await callOpenAI(prompt, options) - - case 'claude': - case 'claude-3': - return await callClaude(prompt, options) - - default: - return await callOllamaModel(prompt, options) - } -} - -// Ollama (local) integration -async function callOllamaModel(prompt, options) { - const baseUrl = options.baseUrl || 'http://localhost:11434' - const model = options.model === 'local' ? 'llama2' : options.model - - try { - const response = await fetch(`${baseUrl}/api/generate`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - model: model, - prompt: prompt, - stream: false - }) - }) - - if (!response.ok) { - throw new Error(`Ollama error: ${response.statusText}. Make sure Ollama is running: ollama serve`) - } - - const data = await response.json() - return data.response || 'No response from local model' - - } catch (error) { - throw new Error(`Local model error: ${error.message}. Try: ollama run llama2`) - } -} - -// OpenAI integration -async function callOpenAI(prompt, options) { - if (!options.apiKey) { - throw new Error('OpenAI API key required. Use --api-key or set OPENAI_API_KEY environment variable') - } - - const model = options.model === 'openai' ? 'gpt-3.5-turbo' : options.model - - try { - const response = await fetch('https://api.openai.com/v1/chat/completions', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${options.apiKey}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - model: model, - messages: [{ role: 'user', content: prompt }], - max_tokens: 500 - }) - }) - - if (!response.ok) { - throw new Error(`OpenAI error: ${response.statusText}`) - } - - const data = await response.json() - return data.choices[0]?.message?.content || 'No response from OpenAI' - - } catch (error) { - throw new Error(`OpenAI error: ${error.message}`) - } -} - -// Claude integration -async function callClaude(prompt, options) { - if (!options.apiKey) { - throw new Error('Anthropic API key required. Use --api-key or set ANTHROPIC_API_KEY environment variable') - } - - try { - const response = await fetch('https://api.anthropic.com/v1/messages', { - method: 'POST', - headers: { - 'x-api-key': options.apiKey, - 'Content-Type': 'application/json', - 'anthropic-version': '2023-06-01' - }, - body: JSON.stringify({ - model: 'claude-3-haiku-20240307', - max_tokens: 500, - messages: [{ role: 'user', content: prompt }] - }) - }) - - if (!response.ok) { - throw new Error(`Claude error: ${response.statusText}`) - } - - const data = await response.json() - return data.content[0]?.text || 'No response from Claude' - - } catch (error) { - throw new Error(`Claude error: ${error.message}`) - } -} - -// ======================================== -// MAIN PROGRAM - CLEAN & SIMPLE -// ======================================== - -program - .name('brainy') - .description('πŸ§ βš›οΈ Brainy - Your AI-Powered Second Brain') - .version(packageJson.version) - .option('-i, --interactive', 'Start interactive mode') - .addHelpText('after', ` -${colors.dim('Examples:')} - ${colors.success('brainy add "Meeting notes from today"')} - ${colors.success('brainy search "project deadline"')} - ${colors.success('brainy chat')} ${colors.dim('# Interactive AI chat')} - ${colors.success('brainy -i')} ${colors.dim('# Interactive mode')} - -${colors.dim('For more help:')} - ${colors.info('brainy --help')} ${colors.dim('# Command-specific help')} - ${colors.info('https://github.com/TimeSoul/brainy')} ${colors.dim('# Documentation')}`) - -// ======================================== -// THE 5 COMMANDS (ONE WAY TO DO EVERYTHING) -// ======================================== - -// Command 0: INIT - Initialize brainy (essential setup) -program - .command('init') - .description('Initialize Brainy in current directory') - .option('-s, --storage ', 'Storage type (filesystem, memory, s3, r2, gcs)') - .option('-e, --encryption', 'Enable encryption for sensitive data') - .option('--s3-bucket ', 'S3 bucket name') - .option('--s3-region ', 'S3 region') - .option('--access-key ', 'Storage access key') - .option('--secret-key ', 'Storage secret key') - .action(wrapAction(async (options) => { - console.log(colors.primary('🧠 Initializing Brainy')) - console.log() - - const { BrainyData } = await import('../dist/brainyData.js') - - const config = { - storage: options.storage || 'filesystem', - encryption: options.encryption || false - } - - // Storage-specific configuration - if (options.storage === 's3' || options.storage === 'r2' || options.storage === 'gcs') { - if (!options.accessKey || !options.secretKey) { - console.log(colors.warning('⚠️ Cloud storage requires access credentials')) - console.log(colors.info('Use: --access-key --secret-key ')) - console.log(colors.info('Or set environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY')) - process.exit(1) - } - - config.storageOptions = { - bucket: options.s3Bucket, - region: options.s3Region || 'us-east-1', - accessKeyId: options.accessKey, - secretAccessKey: options.secretKey - } - } - - try { - const brainy = new BrainyData(config) - await brainy.init() - - console.log(colors.success('βœ… Brainy initialized successfully!')) - console.log(colors.info(`πŸ“ Storage: ${config.storage}`)) - console.log(colors.info(`πŸ”’ Encryption: ${config.encryption ? 'Enabled' : 'Disabled'}`)) - - if (config.encryption) { - console.log(colors.warning('πŸ” Encryption enabled - keep your keys secure!')) - } - - console.log() - console.log(colors.success('πŸš€ Ready to go! Try:')) - console.log(colors.info(' brainy add "Hello, World!"')) - console.log(colors.info(' brainy search "hello"')) - - } catch (error) { - console.log(colors.error('❌ Initialization failed:')) - console.log(colors.error(error.message)) - process.exit(1) - } - })) - -// Command 1: ADD - Add data (smart by default) -program - .command('add [data]') - .description('Add data to your brain (smart auto-detection)') - .option('-m, --metadata ', 'Metadata as JSON') - .option('-i, --id ', 'Custom ID') - .option('--literal', 'Skip AI processing (literal storage)') - .option('--encrypt', 'Encrypt this data (for sensitive information)') - .action(wrapAction(async (data, options) => { - if (!data) { - console.log(colors.info('🧠 Interactive add mode')) - const rl = createInterface({ - input: process.stdin, - output: process.stdout - }) - - data = await new Promise(resolve => { - rl.question(colors.primary('What would you like to add? '), (answer) => { - rl.close() - resolve(answer) - }) - }) - } - - let metadata = {} - if (options.metadata) { - try { - metadata = JSON.parse(options.metadata) - } catch { - console.error(colors.error('Invalid JSON metadata')) - process.exit(1) - } - } - if (options.id) { - metadata.id = options.id - } - if (options.encrypt) { - metadata.encrypted = true - } - - console.log(options.literal - ? colors.info('πŸ”’ Literal storage') - : colors.success('🧠 Smart mode (auto-detects types)') - ) - - if (options.encrypt) { - console.log(colors.warning('πŸ” Encrypting sensitive data...')) - } - - const brainyInstance = await getBrainy() - - // Handle encryption at data level if requested - let processedData = data - if (options.encrypt) { - processedData = await brainyInstance.encryptData(data) - metadata.encrypted = true - } - - const id = await brainyInstance.add({ data: processedData, type: 'content', metadata }) - console.log(colors.success(`βœ… Added successfully! ID: ${id}`)) - })) - -// Command 2: CHAT - Talk to your data with AI -program - .command('chat [message]') - .description('AI chat with your brain data (supports local & cloud models)') - .option('-s, --session ', 'Use specific chat session') - .option('-n, --new', 'Start a new session') - .option('-l, --list', 'List all chat sessions') - .option('-h, --history [limit]', 'Show conversation history (default: 10)') - .option('--search ', 'Search all conversations') - .option('-m, --model ', 'LLM model (local/openai/claude/ollama)', 'local') - .option('--api-key ', 'API key for cloud models') - .option('--base-url ', 'Base URL for local models (default: http://localhost:11434)') - .action(wrapAction(async (message, options) => { - const { BrainyData } = await import('../dist/brainyData.js') - const { BrainyChat } = await import('../dist/chat/BrainyChat.js') - - console.log(colors.primary('πŸ§ πŸ’¬ Brainy Chat - AI-Powered Conversation with Your Data')) - console.log(colors.info('Talk to your brain using your data as context')) - console.log() - - // Initialize brainy and chat - const brainy = new BrainyData() - await brainy.init() - const chat = new BrainyChat(brainy) - - // Handle different options - if (options.list) { - console.log(colors.primary('πŸ“‹ Chat Sessions')) - const sessions = await chat.getSessions(20) - if (sessions.length === 0) { - console.log(colors.warning('No chat sessions found. Start chatting to create your first session!')) - } else { - sessions.forEach((session, i) => { - console.log(colors.success(`${i + 1}. ${session.id}`)) - if (session.title) console.log(colors.info(` Title: ${session.title}`)) - console.log(colors.info(` Messages: ${session.messageCount}`)) - console.log(colors.info(` Last active: ${session.lastMessageAt.toLocaleDateString()}`)) - }) - } - return - } - - if (options.search) { - console.log(colors.primary(`πŸ” Searching conversations for: "${options.search}"`)) - const results = await chat.searchMessages(options.search, { limit: 10 }) - if (results.length === 0) { - console.log(colors.warning('No messages found')) - } else { - results.forEach((msg, i) => { - console.log(colors.success(`\n${i + 1}. [${msg.sessionId}] ${colors.info(msg.speaker)}:`)) - console.log(` ${msg.content.substring(0, 200)}${msg.content.length > 200 ? '...' : ''}`) - }) - } - return - } - - if (options.history) { - const limit = parseInt(options.history) || 10 - console.log(colors.primary(`πŸ“œ Recent Chat History (${limit} messages)`)) - const history = await chat.getHistory(limit) - if (history.length === 0) { - console.log(colors.warning('No chat history found')) - } else { - history.forEach(msg => { - const speaker = msg.speaker === 'user' ? colors.success('You') : colors.info('AI') - console.log(`${speaker}: ${msg.content}`) - console.log(colors.info(` ${msg.timestamp.toLocaleString()}`)) - console.log() - }) - } - return - } - - // Start interactive chat or process single message - if (!message) { - console.log(colors.success('🎯 Interactive mode - type messages or "exit" to quit')) - console.log(colors.info(`Model: ${options.model}`)) - console.log() - - // Auto-discover previous session - const session = options.new ? null : await chat.initialize() - if (session) { - console.log(colors.success(`πŸ“‹ Resumed session: ${session.id}`)) - console.log() - } else { - const newSession = await chat.startNewSession() - console.log(colors.success(`πŸ†• Started new session: ${newSession.id}`)) - console.log() - } - - // Interactive chat loop - const rl = createInterface({ - input: process.stdin, - output: process.stdout, - prompt: colors.primary('You: ') - }) - - rl.prompt() - - rl.on('line', async (input) => { - if (input.trim().toLowerCase() === 'exit') { - console.log(colors.success('πŸ‘‹ Chat session saved to your brain!')) - rl.close() - return - } - - if (input.trim()) { - // Store user message - await chat.addMessage(input.trim(), 'user') - - // Generate AI response - try { - const response = await generateAIResponse(input.trim(), brainy, options) - console.log(colors.info('AI: ') + response) - - // Store AI response - await chat.addMessage(response, 'assistant', { model: options.model }) - console.log() - } catch (error) { - console.log(colors.error('AI Error: ') + error.message) - console.log(colors.warning('πŸ’‘ Tip: Try setting --model local or providing --api-key')) - console.log() - } - } - - rl.prompt() - }) - - rl.on('close', () => { - exitProcess(0) - }) - - } else { - // Single message mode - console.log(colors.success('You: ') + message) - - try { - const response = await generateAIResponse(message, brainy, options) - console.log(colors.info('AI: ') + response) - - // Store conversation - await chat.addMessage(message, 'user') - await chat.addMessage(response, 'assistant', { model: options.model }) - - } catch (error) { - console.log(colors.error('Error: ') + error.message) - console.log(colors.info('πŸ’‘ Try: brainy chat --model local or provide --api-key')) - } - } - })) - -// Command 3: IMPORT - Bulk/external data -program - .command('import [source]') - .description('Import bulk data from files, URLs, or streams') - .option('-t, --type ', 'Source type (file, url, stream)') - .option('-c, --chunk-size ', 'Chunk size for large imports', '1000') - .action(wrapAction(async (source, options) => { - - // Interactive mode if no source provided - if (!source) { - console.log(colors.primary('πŸ“₯ Interactive Import Mode')) - console.log(colors.dim('Import data from various sources\n')) - - const rl = createInterface({ - input: process.stdin, - output: process.stdout - }) - - // Ask for source type first - console.log(colors.cyan('Source types:')) - console.log(colors.info(' 1. Local file')) - console.log(colors.info(' 2. URL')) - console.log(colors.info(' 3. Direct input')) - console.log() - - const sourceType = await new Promise(resolve => { - rl.question(colors.cyan('Select source type (1-3): '), (answer) => { - resolve(answer) - }) - }) - - if (sourceType === '3') { - // Direct input mode - console.log(colors.info('\nEnter your data (type END on a new line when done):\n')) - let data = '' - let line = '' - - while ((line = await new Promise(resolve => { - rl.question('', resolve) - })) !== 'END') { - data += line + '\n' - } - - rl.close() - - // Save to temp file - const fs = require('fs') - source = `/tmp/brainy-import-${Date.now()}.json` - fs.writeFileSync(source, data.trim()) - console.log(colors.info(`\nSaved to temporary file: ${source}`)) - } else { - // File or URL - source = await new Promise(resolve => { - const prompt = sourceType === '2' ? 'Enter URL: ' : 'Enter file path: ' - rl.question(colors.cyan(prompt), (answer) => { - rl.close() - resolve(answer) - }) - }) - - if (!source.trim()) { - console.log(colors.warning('No source provided')) - process.exit(1) - } - } - } - console.log(colors.info('πŸ“₯ Starting neural import...')) - console.log(colors.info(`Source: ${source}`)) - - // Read and prepare data for import - const fs = require('fs') - let data - - try { - if (source.startsWith('http')) { - // Handle URL import - const response = await fetch(source) - data = await response.text() - } else { - // Handle file import - data = fs.readFileSync(source, 'utf8') - } - - // Parse data if JSON - try { - data = JSON.parse(data) - } catch { - // Keep as string if not JSON - } - } catch (error) { - console.log(colors.error(`Failed to read source: ${error.message}`)) - process.exit(1) - } - - const brainyInstance = await getBrainy() - const result = await brainyInstance.import(data, { - batchSize: parseInt(options.chunkSize) || 50 - }) - - console.log(colors.success(`βœ… Imported ${result.length} items`)) - })) - -// Command 3: FIND - Intelligent search using Triple Intelligence -program - .command('find [query]') - .description('Intelligent search using natural language and structured queries') - .option('-l, --limit ', 'Results limit', '10') - .option('-m, --mode ', 'Search mode (auto, semantic, structured)', 'auto') - .option('--like ', 'Vector similarity search term') - .option('--where ', 'Metadata filters as JSON') - .action(wrapAction(async (query, options) => { - - if (!query && !options.like) { - console.log(colors.primary('🧠 Intelligent Find Mode')) - console.log(colors.dim('Use natural language or structured queries\n')) - - const rl = createInterface({ - input: process.stdin, - output: process.stdout - }) - - query = await new Promise(resolve => { - rl.question(colors.cyan('What would you like to find? '), (answer) => { - rl.close() - resolve(answer) - }) - }) - - if (!query.trim()) { - console.log(colors.warning('No query provided')) - process.exit(1) - } - } - - console.log(colors.info(`🧠 Finding: "${query || options.like}"`)) - - const brainyInstance = await getBrainy() - - // Build query object for find() API - let findQuery = query - - // Handle structured queries - if (options.like || options.where) { - findQuery = {} - if (options.like) findQuery.like = options.like - if (options.where) { - try { - findQuery.where = JSON.parse(options.where) - } catch { - console.error(colors.error('Invalid JSON in --where option')) - process.exit(1) - } - } - } - - const findOptions = { - limit: parseInt(options.limit), - mode: options.mode - } - - const results = await brainyInstance.find(findQuery, findOptions) - - if (results.length === 0) { - console.log(colors.warning('No results found')) - return - } - - console.log(colors.success(`βœ… Found ${results.length} intelligent results:`)) - - // Use enhanced formatting with display augmentation - for (let i = 0; i < results.length; i++) { - const formattedResult = await formatResultWithDisplay(results[i], i) - console.log(formattedResult) - } - })) - -// Command 4: SEARCH - Triple-power search -program - .command('search [query]') - .description('Search your brain (vector + graph + facets)') - .option('-l, --limit ', 'Results limit', '10') - .option('-f, --filter ', 'Metadata filters (see "brainy fields" for available fields)') - .option('-d, --depth ', 'Relationship depth', '2') - .option('--fields', 'Show available filter fields and exit') - .action(wrapAction(async (query, options) => { - - // Interactive mode if no query provided - if (!query) { - console.log(colors.primary('πŸ” Interactive Search Mode')) - console.log(colors.dim('Search your neural database with natural language\n')) - - const rl = createInterface({ - input: process.stdin, - output: process.stdout - }) - - query = await new Promise(resolve => { - rl.question(colors.cyan('What would you like to search for? '), (answer) => { - rl.close() - resolve(answer) - }) - }) - - if (!query.trim()) { - console.log(colors.warning('No search query provided')) - process.exit(1) - } - } - - // Handle --fields option - if (options.fields) { - console.log(colors.primary('πŸ” Available Filter Fields')) - console.log(colors.primary('=' .repeat(30))) - - try { - const { BrainyData } = await import('../dist/brainyData.js') - const brainy = new BrainyData() - await brainy.init() - - const filterFields = await brainy.getFilterFields() - if (filterFields.length > 0) { - console.log(colors.success('Available fields for --filter option:')) - filterFields.forEach(field => { - console.log(colors.info(` ${field}`)) - }) - console.log() - console.log(colors.primary('Usage Examples:')) - console.log(colors.info(` brainy search "query" --filter '{"type":"person"}'`)) - console.log(colors.info(` brainy search "query" --filter '{"category":"work","status":"active"}'`)) - } else { - console.log(colors.warning('No indexed fields available yet.')) - console.log(colors.info('Add some data with metadata to see available fields.')) - } - - } catch (error) { - console.log(colors.error(`Error: ${error.message}`)) - } - return - } - console.log(colors.info(`πŸ” Searching: "${query}"`)) - - const searchOptions = { - limit: parseInt(options.limit), - depth: parseInt(options.depth) - } - - if (options.filter) { - try { - searchOptions.filter = JSON.parse(options.filter) - } catch { - console.error(colors.error('Invalid filter JSON')) - process.exit(1) - } - } - - const brainyInstance = await getBrainy() - const results = await brainyInstance.search(query, searchOptions) - - if (results.length === 0) { - console.log(colors.warning('No results found')) - return - } - - console.log(colors.success(`βœ… Found ${results.length} results:`)) - - // Use enhanced formatting with display augmentation - for (let i = 0; i < results.length; i++) { - const formattedResult = await formatResultWithDisplay(results[i], i) - console.log(formattedResult) - } - })) - -// Command 4: GET - Retrieve specific data by ID -program - .command('get [id]') - .description('Get a specific item by ID') - .option('-f, --format ', 'Output format (json, table, plain)', 'plain') - .option('--display-debug', 'Show debug information about display augmentation') - .action(wrapAction(async (id, options) => { - if (!id) { - console.log(colors.primary('πŸ” Interactive Get Mode')) - console.log(colors.dim('Retrieve a specific item by ID\n')) - - const rl = createInterface({ - input: process.stdin, - output: process.stdout - }) - - id = await new Promise(resolve => { - rl.question(colors.cyan('Enter item ID: '), (answer) => { - rl.close() - resolve(answer) - }) - }) - - if (!id.trim()) { - console.log(colors.warning('No ID provided')) - process.exit(1) - } - } - - console.log(colors.info(`πŸ” Getting item: "${id}"`)) - - const brainyInstance = await getBrainy() - const item = await brainyInstance.getNoun(id) - - if (!item) { - console.log(colors.warning('Item not found')) - return - } - - // Show display debug information if requested - if (options.displayDebug) { - console.log(colors.primary('πŸ” Display Augmentation Debug Information')) - console.log('=' .repeat(50)) - - try { - if (item.getDisplay && typeof item.getDisplay === 'function') { - console.log(colors.success('βœ… Display augmentation active')) - - const displayFields = await item.getDisplay() - console.log(colors.info('\n🎨 Computed Display Fields:')) - Object.entries(displayFields).forEach(([key, value]) => { - console.log(colors.cyan(` ${key}: ${JSON.stringify(value)}`)) - }) - - // Show available fields - if (item.getAvailableFields && typeof item.getAvailableFields === 'function') { - const availableFields = item.getAvailableFields('display') - console.log(colors.info('\nπŸ“‹ Available Display Fields:')) - availableFields.forEach(field => { - console.log(colors.dim(` - ${field}`)) - }) - } - - // Show augmentation info - if (item.getAvailableAugmentations && typeof item.getAvailableAugmentations === 'function') { - const augs = item.getAvailableAugmentations() - console.log(colors.info('\nπŸ”Œ Available Augmentations:')) - augs.forEach(aug => { - console.log(colors.dim(` - ${aug}`)) - }) - } - } else { - console.log(colors.warning('⚠️ Display augmentation not active or not enhanced')) - console.log(colors.dim(' Item does not have getDisplay() method')) - } - } catch (error) { - console.log(colors.error(`❌ Display debug error: ${error.message}`)) - } - - console.log('\n' + '=' .repeat(50)) - } - - // Use enhanced formatting with display augmentation - const formattedItem = await formatItemWithDisplay(item, options.format) - console.log(formattedItem) - })) - -// Command 5: UPDATE - Update existing data -program - .command('update [id]') - .description('Update existing data with new content or metadata') - .option('-d, --data ', 'New data content') - .option('-m, --metadata ', 'New metadata as JSON') - .option('--no-merge', 'Replace metadata instead of merging') - .option('--no-reindex', 'Skip reindexing (faster but less accurate search)') - .option('--cascade', 'Update related verbs') - .action(wrapAction(async (id, options) => { - - // Interactive mode if no ID provided - if (!id) { - console.log(colors.primary('πŸ”„ Interactive Update Mode')) - console.log(colors.dim('Select an item to update\n')) - - // Show recent items - const brainyInstance = await getBrainy() - const recent = await brainyInstance.search('*', { limit: 10, sortBy: 'timestamp' }) - - if (recent.length > 0) { - console.log(colors.cyan('Recent items:')) - - // Enhanced display for recent items - for (let i = 0; i < Math.min(recent.length, 10); i++) { - const item = recent[i] - try { - if (item.getDisplay && typeof item.getDisplay === 'function') { - const displayFields = await item.getDisplay() - console.log(colors.info(` ${i + 1}. ${item.id} - ${displayFields.title}`)) - } else { - console.log(colors.info(` ${i + 1}. ${item.id} - ${item.content?.substring(0, 50)}...`)) - } - } catch { - console.log(colors.info(` ${i + 1}. ${item.id} - ${item.content?.substring(0, 50)}...`)) - } - } - console.log() - } - - const rl = createInterface({ - input: process.stdin, - output: process.stdout - }) - - id = await new Promise(resolve => { - rl.question(colors.cyan('Enter ID to update: '), (answer) => { - rl.close() - resolve(answer) - }) - }) - - if (!id.trim()) { - console.log(colors.warning('No ID provided')) - process.exit(1) - } - } - console.log(colors.info(`πŸ”„ Updating: "${id}"`)) - - if (!options.data && !options.metadata) { - console.error(colors.error('Error: Must provide --data or --metadata')) - process.exit(1) - } - - let metadata = undefined - if (options.metadata) { - try { - metadata = JSON.parse(options.metadata) - } catch { - console.error(colors.error('Invalid JSON metadata')) - process.exit(1) - } - } - - const brainyInstance = await getBrainy() - - const success = await brainyInstance.updateNoun(id, options.data, metadata, { - merge: options.merge !== false, // Default true unless --no-merge - reindex: options.reindex !== false, // Default true unless --no-reindex - cascade: options.cascade || false - }) - - if (success) { - console.log(colors.success('βœ… Updated successfully!')) - if (options.cascade) { - console.log(colors.info('πŸ“Ž Related verbs updated')) - } - } else { - console.log(colors.error('❌ Update failed')) - } - })) - -// Command 5: DELETE - Remove data (soft delete by default) -program - .command('delete [id]') - .description('Delete data (soft delete by default, preserves indexes)') - .option('--hard', 'Permanent deletion (removes from indexes)') - .option('--cascade', 'Delete related verbs') - .option('--force', 'Force delete even if has relationships') - .action(wrapAction(async (id, options) => { - - // Interactive mode if no ID provided - if (!id) { - console.log(colors.warning('πŸ—‘οΈ Interactive Delete Mode')) - console.log(colors.dim('Select an item to delete\n')) - - // Show recent items for selection - const brainyInstance = await getBrainy() - const recent = await brainyInstance.search('*', { limit: 10, sortBy: 'timestamp' }) - - if (recent.length > 0) { - console.log(colors.cyan('Recent items:')) - - // Enhanced display for recent items - for (let i = 0; i < Math.min(recent.length, 10); i++) { - const item = recent[i] - try { - if (item.getDisplay && typeof item.getDisplay === 'function') { - const displayFields = await item.getDisplay() - console.log(colors.info(` ${i + 1}. ${item.id} - ${displayFields.title}`)) - } else { - console.log(colors.info(` ${i + 1}. ${item.id} - ${item.content?.substring(0, 50)}...`)) - } - } catch { - console.log(colors.info(` ${i + 1}. ${item.id} - ${item.content?.substring(0, 50)}...`)) - } - } - console.log() - } - - const rl = createInterface({ - input: process.stdin, - output: process.stdout - }) - - id = await new Promise(resolve => { - rl.question(colors.warning('Enter ID to delete (or "cancel"): '), (answer) => { - rl.close() - resolve(answer) - }) - }) - - if (!id.trim() || id.toLowerCase() === 'cancel') { - console.log(colors.info('Delete cancelled')) - process.exit(0) - } - - // Confirm deletion in interactive mode - const confirmRl = createInterface({ - input: process.stdin, - output: process.stdout - }) - - const confirm = await new Promise(resolve => { - const deleteType = options.hard ? 'permanently delete' : 'soft delete' - confirmRl.question(colors.warning(`Are you sure you want to ${deleteType} "${id}"? (yes/no): `), (answer) => { - confirmRl.close() - resolve(answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y') - }) - }) - - if (!confirm) { - console.log(colors.info('Delete cancelled')) - process.exit(0) - } - } - console.log(colors.info(`πŸ—‘οΈ Deleting: "${id}"`)) - - if (options.hard) { - console.log(colors.warning('⚠️ Hard delete - data will be permanently removed')) - } else { - console.log(colors.info('πŸ”’ Soft delete - data marked as deleted but preserved')) - } - - const brainyInstance = await getBrainy() - - try { - const success = await brainyInstance.deleteNoun(id, { - soft: !options.hard, // Soft delete unless --hard specified - cascade: options.cascade || false, - force: options.force || false - }) - - if (success) { - console.log(colors.success('βœ… Deleted successfully!')) - if (options.cascade) { - console.log(colors.info('πŸ“Ž Related verbs also deleted')) - } - } else { - console.log(colors.error('❌ Delete failed')) - } - } catch (error) { - console.error(colors.error(`❌ Delete failed: ${error.message}`)) - if (error.message.includes('has relationships')) { - console.log(colors.info('πŸ’‘ Try: --cascade to delete relationships or --force to ignore them')) - } - } - })) - -// Command 6A: ADD-NOUN - Create typed entities (Method #4) -program - .command('add-noun [name]') - .description('Add a typed entity to your knowledge graph') - .option('-t, --type ', 'Noun type (Person, Organization, Project, Event, Concept, Location, Product)', 'Concept') - .option('-m, --metadata ', 'Metadata as JSON') - .option('--encrypt', 'Encrypt this entity') - .action(wrapAction(async (name, options) => { - - // Interactive mode if no name provided - if (!name) { - console.log(colors.primary('πŸ‘€ Interactive Entity Creation')) - console.log(colors.dim('Create a typed entity in your knowledge graph\n')) - - const rl = createInterface({ - input: process.stdin, - output: process.stdout - }) - - name = await new Promise(resolve => { - rl.question(colors.cyan('Enter entity name: '), (answer) => { - resolve(answer) - }) - }) - - if (!name.trim()) { - rl.close() - console.log(colors.warning('No name provided')) - process.exit(1) - } - - // Interactive type selection if not provided - if (!options.type || options.type === 'Concept') { - console.log(colors.cyan('\nSelect entity type:')) - const types = ['Person', 'Organization', 'Project', 'Event', 'Concept', 'Location', 'Product'] - types.forEach((t, i) => { - console.log(colors.info(` ${i + 1}. ${t}`)) - }) - console.log() - - const typeIndex = await new Promise(resolve => { - rl.question(colors.cyan('Select type (1-7): '), (answer) => { - resolve(parseInt(answer) - 1) - }) - }) - - if (typeIndex >= 0 && typeIndex < types.length) { - options.type = types[typeIndex] - } - } - - rl.close() - } - const brainy = await getBrainy() - - // Validate noun type - const validTypes = ['Person', 'Organization', 'Project', 'Event', 'Concept', 'Location', 'Product'] - if (!validTypes.includes(options.type)) { - console.log(colors.error(`❌ Invalid noun type: ${options.type}`)) - console.log(colors.info(`Valid types: ${validTypes.join(', ')}`)) - process.exit(1) - } - - let metadata = {} - if (options.metadata) { - try { - metadata = JSON.parse(options.metadata) - } catch { - console.error(colors.error('❌ Invalid JSON metadata')) - process.exit(1) - } - } - - if (options.encrypt) { - metadata.encrypted = true - } - - try { - // Use modern 3.0 API with parameter object - const id = await brainy.add({ data: name, type: options.type, metadata }) - - console.log(colors.success('βœ… Noun added successfully!')) - console.log(colors.info(`πŸ†” ID: ${id}`)) - console.log(colors.info(`πŸ‘€ Name: ${name}`)) - console.log(colors.info(`🏷️ Type: ${options.type}`)) - if (Object.keys(metadata).length > 0) { - console.log(colors.info(`πŸ“ Metadata: ${JSON.stringify(metadata, null, 2)}`)) - } - } catch (error) { - console.log(colors.error('❌ Failed to add noun:')) - console.log(colors.error(error.message)) - process.exit(1) - } - })) - -// Command 6B: ADD-VERB - Create relationships (Method #5) -program - .command('add-verb [source] [target]') - .description('Create a relationship between two entities') - .option('-t, --type ', 'Verb type (WorksFor, Knows, CreatedBy, BelongsTo, Uses, etc.)', 'RelatedTo') - .option('-m, --metadata ', 'Relationship metadata as JSON') - .option('--encrypt', 'Encrypt this relationship') - .action(wrapAction(async (source, target, options) => { - - // Interactive mode if parameters missing - if (!source || !target) { - console.log(colors.primary('πŸ”— Interactive Relationship Builder')) - console.log(colors.dim('Connect two entities with a semantic relationship\n')) - - const brainyInstance = await getBrainy() - const rl = createInterface({ - input: process.stdin, - output: process.stdout - }) - - // Get source if not provided - if (!source) { - // Show recent items - const recent = await brainyInstance.search('*', { limit: 10, sortBy: 'timestamp' }) - if (recent.length > 0) { - console.log(colors.cyan('Recent items (source):')) - - // Enhanced display for recent items - for (let i = 0; i < Math.min(recent.length, 10); i++) { - const item = recent[i] - try { - if (item.getDisplay && typeof item.getDisplay === 'function') { - const displayFields = await item.getDisplay() - console.log(colors.info(` ${i + 1}. ${displayFields.icon} ${item.id} - ${displayFields.title}`)) - } else { - console.log(colors.info(` ${i + 1}. ${item.id} - ${item.content?.substring(0, 40)}...`)) - } - } catch { - console.log(colors.info(` ${i + 1}. ${item.id} - ${item.content?.substring(0, 40)}...`)) - } - } - console.log() - } - - source = await new Promise(resolve => { - rl.question(colors.cyan('Enter source entity ID: '), (answer) => { - resolve(answer) - }) - }) - - if (!source.trim()) { - rl.close() - console.log(colors.warning('No source provided')) - process.exit(1) - } - } - - // Interactive verb type selection - if (!options.type || options.type === 'RelatedTo') { - console.log(colors.cyan('\nSelect relationship type:')) - const verbs = ['WorksFor', 'Knows', 'CreatedBy', 'BelongsTo', 'Uses', 'Manages', 'LocatedIn', 'RelatedTo', 'Custom...'] - verbs.forEach((v, i) => { - console.log(colors.info(` ${i + 1}. ${v}`)) - }) - console.log() - - const verbIndex = await new Promise(resolve => { - rl.question(colors.cyan('Select type (1-9): '), (answer) => { - resolve(parseInt(answer) - 1) - }) - }) - - if (verbIndex >= 0 && verbIndex < verbs.length - 1) { - options.type = verbs[verbIndex] - } else if (verbIndex === verbs.length - 1) { - // Custom verb - options.type = await new Promise(resolve => { - rl.question(colors.cyan('Enter custom relationship: '), (answer) => { - resolve(answer) - }) - }) - } - } - - // Get target if not provided - if (!target) { - // Show recent items again - const recent = await brainyInstance.search('*', { limit: 10, sortBy: 'timestamp' }) - if (recent.length > 0) { - console.log(colors.cyan('\nRecent items (target):')) - - // Enhanced display for recent items - for (let i = 0; i < Math.min(recent.length, 10); i++) { - const item = recent[i] - try { - if (item.getDisplay && typeof item.getDisplay === 'function') { - const displayFields = await item.getDisplay() - console.log(colors.info(` ${i + 1}. ${displayFields.icon} ${item.id} - ${displayFields.title}`)) - } else { - console.log(colors.info(` ${i + 1}. ${item.id} - ${item.content?.substring(0, 40)}...`)) - } - } catch { - console.log(colors.info(` ${i + 1}. ${item.id} - ${item.content?.substring(0, 40)}...`)) - } - } - console.log() - } - - target = await new Promise(resolve => { - rl.question(colors.cyan('Enter target entity ID: '), (answer) => { - resolve(answer) - }) - }) - - if (!target.trim()) { - rl.close() - console.log(colors.warning('No target provided')) - process.exit(1) - } - } - - rl.close() - } - const brainy = await getBrainy() - - // Common verb types for validation - const commonTypes = ['WorksFor', 'Knows', 'CreatedBy', 'BelongsTo', 'Uses', 'LeadsProject', 'MemberOf', 'RelatedTo', 'InteractedWith'] - if (!commonTypes.includes(options.type)) { - console.log(colors.warning(`⚠️ Uncommon verb type: ${options.type}`)) - console.log(colors.info(`Common types: ${commonTypes.join(', ')}`)) - } - - let metadata = {} - if (options.metadata) { - try { - metadata = JSON.parse(options.metadata) - } catch { - console.error(colors.error('❌ Invalid JSON metadata')) - process.exit(1) - } - } - - if (options.encrypt) { - metadata.encrypted = true - } - - try { - const { VerbType } = await import('../dist/types/graphTypes.js') - - // Use the provided type or fall back to RelatedTo - const verbType = VerbType[options.type] || options.type - const id = await brainy.relate({ from: source, to: target, type: verbType, metadata }) - - console.log(colors.success('βœ… Relationship added successfully!')) - console.log(colors.info(`πŸ†” ID: ${id}`)) - console.log(colors.info(`πŸ”— ${source} --[${options.type}]--> ${target}`)) - if (Object.keys(metadata).length > 0) { - console.log(colors.info(`πŸ“ Metadata: ${JSON.stringify(metadata, null, 2)}`)) - } - } catch (error) { - console.log(colors.error('❌ Failed to add relationship:')) - console.log(colors.error(error.message)) - process.exit(1) - } - })) - -// Command 7: STATUS - Database health & info -program - .command('status') - .description('Show brain status and comprehensive statistics') - .option('-v, --verbose', 'Show raw JSON statistics') - .option('-s, --simple', 'Show only basic info') - .action(wrapAction(async (options) => { - console.log(colors.primary('🧠 Brain Status & Statistics')) - console.log(colors.primary('=' .repeat(50))) - - try { - const { BrainyData } = await import('../dist/brainyData.js') - const brainy = new BrainyData() - await brainy.init() - - // Get comprehensive stats - const stats = await brainy.getStatistics() - const memUsage = process.memoryUsage() - - // Basic Health Status - console.log(colors.success('πŸ’š Status: Healthy')) - console.log(colors.info(`πŸš€ Version: ${packageJson.version}`)) - console.log() - - if (options.simple) { - console.log(colors.info(`πŸ“Š Total Items: ${stats.total || 0}`)) - console.log(colors.info(`🧠 Memory: ${(memUsage.heapUsed / 1024 / 1024).toFixed(1)} MB`)) - return - } - - // Core Statistics - console.log(colors.primary('πŸ“Š Core Database Statistics')) - console.log(colors.info(` Total Items: ${colors.success(stats.total || 0)}`)) - console.log(colors.info(` Nouns: ${colors.success(stats.nounCount || 0)}`)) - console.log(colors.info(` Verbs (Relationships): ${colors.success(stats.verbCount || 0)}`)) - console.log(colors.info(` Metadata Records: ${colors.success(stats.metadataCount || 0)}`)) - console.log() - - // Per-Service Breakdown (if available) - if (stats.serviceBreakdown && Object.keys(stats.serviceBreakdown).length > 0) { - console.log(colors.primary('πŸ”§ Per-Service Breakdown')) - Object.entries(stats.serviceBreakdown).forEach(([service, serviceStats]) => { - console.log(colors.info(` ${colors.success(service)}:`)) - console.log(colors.info(` Nouns: ${serviceStats.nounCount}`)) - console.log(colors.info(` Verbs: ${serviceStats.verbCount}`)) - console.log(colors.info(` Metadata: ${serviceStats.metadataCount}`)) - }) - console.log() - } - - // Storage Information - if (stats.storage) { - console.log(colors.primary('πŸ’Ύ Storage Information')) - console.log(colors.info(` Type: ${colors.success(stats.storage.type || 'Unknown')}`)) - if (stats.storage.size) { - const sizeInMB = (stats.storage.size / 1024 / 1024).toFixed(2) - console.log(colors.info(` Size: ${colors.success(sizeInMB)} MB`)) - } - if (stats.storage.location) { - console.log(colors.info(` Location: ${colors.success(stats.storage.location)}`)) - } - console.log() - } - - // Performance Metrics - if (stats.performance) { - console.log(colors.primary('⚑ Performance Metrics')) - if (stats.performance.avgQueryTime) { - console.log(colors.info(` Avg Query Time: ${colors.success(stats.performance.avgQueryTime.toFixed(2))} ms`)) - } - if (stats.performance.totalQueries) { - console.log(colors.info(` Total Queries: ${colors.success(stats.performance.totalQueries)}`)) - } - if (stats.performance.cacheHitRate) { - console.log(colors.info(` Cache Hit Rate: ${colors.success((stats.performance.cacheHitRate * 100).toFixed(1))}%`)) - } - console.log() - } - - // Vector Index Information - if (stats.index) { - console.log(colors.primary('🎯 Vector Index')) - console.log(colors.info(` Dimensions: ${colors.success(stats.index.dimensions || 'N/A')}`)) - console.log(colors.info(` Indexed Vectors: ${colors.success(stats.index.vectorCount || 0)}`)) - if (stats.index.indexSize) { - console.log(colors.info(` Index Size: ${colors.success((stats.index.indexSize / 1024 / 1024).toFixed(2))} MB`)) - } - console.log() - } - - // Memory Usage Breakdown - console.log(colors.primary('🧠 Memory Usage')) - console.log(colors.info(` Heap Used: ${colors.success((memUsage.heapUsed / 1024 / 1024).toFixed(1))} MB`)) - console.log(colors.info(` Heap Total: ${colors.success((memUsage.heapTotal / 1024 / 1024).toFixed(1))} MB`)) - console.log(colors.info(` RSS: ${colors.success((memUsage.rss / 1024 / 1024).toFixed(1))} MB`)) - console.log() - - // Active Augmentations - console.log(colors.primary('πŸ”Œ Active Augmentations')) - try { - // Check for display augmentation specifically - const displayAugmentation = (brainy as any).augmentations?.get('display') - if (displayAugmentation) { - console.log(colors.success(` βœ… display - Universal Display Augmentation`)) - console.log(colors.info(` 🎨 AI-powered titles and descriptions`)) - - // Get display augmentation stats if available - if (displayAugmentation.getStats) { - const stats = displayAugmentation.getStats() - if (stats.totalComputations > 0) { - console.log(colors.dim(` πŸ“Š ${stats.totalComputations} computations, ${(stats.cacheHitRatio * 100).toFixed(1)}% cache hit rate`)) - } - } - } - - // Show other augmentations - const otherAugs = (brainy as any).augmentations ? - Array.from((brainy as any).augmentations.values()).filter((aug: any) => aug.name !== 'display') : - [] - - otherAugs.forEach((aug: any) => { - console.log(colors.success(` βœ… ${aug.name}`)) - if (aug.version) { - console.log(colors.info(` v${aug.version} - ${aug.description || 'No description'}`)) - } - }) - - if (!displayAugmentation && otherAugs.length === 0) { - console.log(colors.warning(' No augmentations currently active')) - } - } catch (error) { - console.log(colors.warning(' Augmentation status unavailable')) - } - console.log() - - // Configuration Summary - if (stats.config) { - console.log(colors.primary('βš™οΈ Configuration')) - Object.entries(stats.config).forEach(([key, value]) => { - // Don't show sensitive values - if (key.toLowerCase().includes('key') || key.toLowerCase().includes('secret')) { - console.log(colors.info(` ${key}: ${colors.warning('[HIDDEN]')}`)) - } else { - console.log(colors.info(` ${key}: ${colors.success(value)}`)) - } - }) - console.log() - } - - // Available Fields for Advanced Search - console.log(colors.primary('πŸ” Available Search Fields')) - try { - const filterFields = await brainy.getFilterFields() - if (filterFields.length > 0) { - console.log(colors.info(' Use these fields for advanced filtering:')) - filterFields.forEach(field => { - console.log(colors.success(` ${field}`)) - }) - console.log(colors.info('\n Example: brainy search "query" --filter \'{"type":"person"}\'')) - } else { - console.log(colors.warning(' No indexed fields available yet')) - console.log(colors.info(' Add some data to see available fields')) - } - } catch (error) { - console.log(colors.warning(' Field discovery not available')) - } - console.log() - - // Show raw JSON if verbose - if (options.verbose) { - console.log(colors.primary('πŸ“‹ Raw Statistics (JSON)')) - console.log(colors.info(JSON.stringify(stats, null, 2))) - } - - } catch (error) { - console.log(colors.error('❌ Status: Error')) - console.log(colors.error(`Error: ${error.message}`)) - if (options.verbose) { - console.log(colors.error('Stack trace:')) - console.log(error.stack) - } - } - })) - -// Command 5: CONFIG - Essential configuration -program - .command('config [key] [value]') - .description('Configure brainy (get, set, list)') - .action(wrapAction(async (action, key, value) => { - const configActions = { - get: async () => { - if (!key) { - console.error(colors.error('Please specify a key: brainy config get ')) - process.exit(1) - } - const result = await cortex.configGet(key) - console.log(colors.success(`${key}: ${result || 'not set'}`)) - }, - set: async () => { - if (!key || !value) { - console.error(colors.error('Usage: brainy config set ')) - process.exit(1) - } - await cortex.configSet(key, value) - console.log(colors.success(`βœ… Set ${key} = ${value}`)) - }, - list: async () => { - const config = await cortex.configList() - console.log(colors.primary('πŸ”§ Current Configuration:')) - Object.entries(config).forEach(([k, v]) => { - console.log(colors.info(` ${k}: ${v}`)) - }) - } - } - - if (configActions[action]) { - await configActions[action]() - } else { - console.error(colors.error('Valid actions: get, set, list')) - process.exit(1) - } - })) - -// Command 6: AUGMENT - Manage augmentations (The 8th Unified Method!) -program - .command('augment ') - .description('Manage augmentations to extend Brainy\'s capabilities') - .option('-n, --name ', 'Augmentation name') - .option('-t, --type ', 'Augmentation type (sense, conduit, cognition, memory)') - .option('-p, --path ', 'Path to augmentation module') - .option('-l, --list', 'List all augmentations') - .action(wrapAction(async (action, options) => { - const brainy = await initBrainy() - console.log(colors.brain('🧩 Augmentation Management')) - - const actions = { - list: async () => { - try { - // Use soulcraft.com registry API - const REGISTRY_URL = 'https://api.soulcraft.com/v1/augmentations' - const response = await fetch(REGISTRY_URL) - - if (response && response.ok) { - console.log(colors.brain('🏒 SOULCRAFT PROFESSIONAL SUITE\n')) - - const data = await response.json() - const augmentations = data.data || [] // NEW: data is in .data array - - // Get local augmentations to check what's installed - const localAugmentations = brainy.listAugmentations() - const localPackageNames = localAugmentations.map(aug => aug.package || aug.name).filter(Boolean) - - // Find installed registry augmentations - const installed = augmentations.filter(aug => - aug.package && localPackageNames.includes(aug.package) - ) - - // Display installed augmentations first - if (installed.length > 0) { - console.log(colors.success('βœ… INSTALLED AUGMENTATIONS')) - installed.forEach(aug => { - const pricing = aug.price - ? `$${aug.price.monthly}/mo` - : (aug.tier === 'free' ? 'FREE' : 'TBD') - const pricingColor = aug.tier === 'free' ? colors.success(pricing) : colors.yellow(pricing) - - console.log(` ${aug.name.padEnd(20)} ${pricingColor.padEnd(15)} ${colors.success('βœ… ACTIVE')}`) - console.log(` ${colors.dim(aug.description)}`) - console.log('') - }) - console.log('') // Extra space before available augmentations - } - - // Filter out installed ones from the available lists - const availableAugmentations = augmentations.filter(aug => - !installed.find(inst => inst.id === aug.id) - ) - - // NEW: Use new tier names - "premium" instead of "professional" - const premium = availableAugmentations.filter(a => a.tier === 'premium') - const free = availableAugmentations.filter(a => a.tier === 'free') - const community = availableAugmentations.filter(a => a.tier === 'community') - const comingSoon = availableAugmentations.filter(a => a.status === 'coming_soon') - - // Display premium augmentations - if (premium.length > 0) { - console.log(colors.primary('πŸš€ PREMIUM AUGMENTATIONS')) - premium.forEach(aug => { - // NEW: price object format - const pricing = aug.price - ? `$${aug.price.monthly}/mo` - : (aug.tier === 'free' ? 'FREE' : 'TBD') - const pricingColor = aug.tier === 'free' ? colors.success(pricing) : colors.yellow(pricing) - - // NEW: status instead of verified - const status = aug.status === 'available' ? colors.blue('βœ“') : - aug.status === 'coming_soon' ? colors.yellow('⏳') : - colors.dim('β€’') - - console.log(` ${aug.name.padEnd(20)} ${pricingColor.padEnd(15)} ${status}`) - console.log(` ${colors.dim(aug.description)}`) - - if (aug.status === 'coming_soon' && aug.eta) { - console.log(` ${colors.cyan('β†’ Coming ' + aug.eta)}`) - } - - if (aug.features && aug.features.length > 0) { - console.log(` ${colors.cyan('β†’ ' + aug.features.join(', '))}`) - } - console.log('') - }) - } - - // Display free augmentations - if (free.length > 0) { - console.log(colors.primary('πŸ†“ FREE AUGMENTATIONS')) - free.forEach(aug => { - const status = aug.status === 'available' ? colors.blue('βœ“') : - aug.status === 'coming_soon' ? colors.yellow('⏳') : - colors.dim('β€’') - - console.log(` ${aug.name.padEnd(20)} ${colors.success('FREE').padEnd(15)} ${status}`) - console.log(` ${colors.dim(aug.description)}`) - - if (aug.status === 'coming_soon' && aug.eta) { - console.log(` ${colors.cyan('β†’ Coming ' + aug.eta)}`) - } - console.log('') - }) - } - - // Display community augmentations - if (community.length > 0) { - console.log(colors.primary('πŸ‘₯ COMMUNITY AUGMENTATIONS')) - community.forEach(aug => { - const status = aug.status === 'available' ? colors.blue('βœ“') : - aug.status === 'coming_soon' ? colors.yellow('⏳') : - colors.dim('β€’') - - console.log(` ${aug.name.padEnd(20)} ${colors.success('COMMUNITY').padEnd(15)} ${status}`) - console.log(` ${colors.dim(aug.description)}`) - console.log('') - }) - } - - // Display truly local (non-registry) augmentations - const localOnly = localAugmentations.filter(aug => - !aug.package || !augmentations.find(regAug => regAug.package === aug.package) - ) - - if (localOnly.length > 0) { - console.log(colors.primary('πŸ“¦ CUSTOM AUGMENTATIONS')) - localOnly.forEach(aug => { - const status = aug.enabled ? colors.success('βœ… Enabled') : colors.dim('βšͺ Disabled') - console.log(` ${aug.name.padEnd(20)} ${status}`) - console.log(` ${colors.dim(aug.description || 'Custom augmentation')}`) - console.log('') - }) - } - - console.log(colors.cyan('🎯 GET STARTED')) - console.log(' brainy install Install augmentation') - console.log(' brainy cloud Access Brain Cloud features') - console.log(` ${colors.blue('Learn more:')} https://soulcraft.com/augmentations`) - - } else { - throw new Error('Registry unavailable') - } - } catch (error) { - // Fallback to local augmentations only - console.log(colors.warning('⚠ Professional catalog unavailable, showing local augmentations')) - const augmentations = brainy.listAugmentations() - if (augmentations.length === 0) { - console.log(colors.warning('No augmentations registered')) - return - } - - const table = new Table({ - head: [colors.brain('Name'), colors.brain('Type'), colors.brain('Status'), colors.brain('Description')], - style: { head: [], border: [] } - }) - - augmentations.forEach(aug => { - table.push([ - colors.primary(aug.name), - colors.info(aug.type), - aug.enabled ? colors.success('βœ… Enabled') : colors.dim('βšͺ Disabled'), - colors.dim(aug.description || '') - ]) - }) - - console.log(table.toString()) - console.log(colors.info(`\nTotal: ${augmentations.length} augmentations`)) - } - }, - - enable: async () => { - if (!options.name) { - console.log(colors.error('Name required: --name ')) - return - } - const success = brainy.enableAugmentation(options.name) - if (success) { - console.log(colors.success(`βœ… Enabled augmentation: ${options.name}`)) - } else { - console.log(colors.error(`Failed to enable: ${options.name} (not found)`)) - } - }, - - disable: async () => { - if (!options.name) { - console.log(colors.error('Name required: --name ')) - return - } - const success = brainy.disableAugmentation(options.name) - if (success) { - console.log(colors.warning(`βšͺ Disabled augmentation: ${options.name}`)) - } else { - console.log(colors.error(`Failed to disable: ${options.name} (not found)`)) - } - }, - - register: async () => { - if (!options.path) { - console.log(colors.error('Path required: --path ')) - return - } - - try { - // Dynamic import of custom augmentation - const customModule = await import(options.path) - const AugmentationClass = customModule.default || customModule[Object.keys(customModule)[0]] - - if (!AugmentationClass) { - console.log(colors.error('No augmentation class found in module')) - return - } - - const augmentation = new AugmentationClass() - brainy.register(augmentation) - console.log(colors.success(`βœ… Registered augmentation: ${augmentation.name}`)) - console.log(colors.info(`Type: ${augmentation.type}`)) - if (augmentation.description) { - console.log(colors.dim(`Description: ${augmentation.description}`)) - } - } catch (error) { - console.log(colors.error(`Failed to register augmentation: ${error.message}`)) - } - }, - - unregister: async () => { - if (!options.name) { - console.log(colors.error('Name required: --name ')) - return - } - - brainy.unregister(options.name) - console.log(colors.warning(`πŸ—‘οΈ Unregistered augmentation: ${options.name}`)) - }, - - 'enable-type': async () => { - if (!options.type) { - console.log(colors.error('Type required: --type ')) - console.log(colors.info('Valid types: sense, conduit, cognition, memory, perception, dialog, activation')) - return - } - - const count = brainy.enableAugmentationType(options.type) - console.log(colors.success(`βœ… Enabled ${count} ${options.type} augmentations`)) - }, - - 'disable-type': async () => { - if (!options.type) { - console.log(colors.error('Type required: --type ')) - console.log(colors.info('Valid types: sense, conduit, cognition, memory, perception, dialog, activation')) - return - } - - const count = brainy.disableAugmentationType(options.type) - console.log(colors.warning(`βšͺ Disabled ${count} ${options.type} augmentations`)) - } - } - - if (actions[action]) { - await actions[action]() - } else { - console.log(colors.error('Valid actions: list, enable, disable, register, unregister, enable-type, disable-type')) - console.log(colors.info('\nExamples:')) - console.log(colors.dim(' brainy augment list # List all augmentations')) - console.log(colors.dim(' brainy augment enable --name neural-import # Enable an augmentation')) - console.log(colors.dim(' brainy augment register --path ./my-augmentation.js # Register custom augmentation')) - console.log(colors.dim(' brainy augment enable-type --type sense # Enable all sense augmentations')) - } - })) - -// Command 7: CLEAR - Clear all data -program - .command('clear') - .description('Clear all data from your brain (with safety prompt)') - .option('--force', 'Force clear without confirmation') - .option('--backup', 'Create backup before clearing') - .action(wrapAction(async (options) => { - if (!options.force) { - console.log(colors.warning('🚨 This will delete ALL data in your brain!')) - - const rl = createInterface({ - input: process.stdin, - output: process.stdout - }) - - const confirmed = await new Promise(resolve => { - rl.question(colors.warning('Type "DELETE EVERYTHING" to confirm: '), (answer) => { - rl.close() - resolve(answer === 'DELETE EVERYTHING') - }) - }) - - if (!confirmed) { - console.log(colors.info('Clear operation cancelled')) - return - } - } - - const brainyInstance = await getBrainy() - - if (options.backup) { - console.log(colors.info('πŸ’Ύ Creating backup...')) - // Future: implement backup functionality - console.log(colors.success('βœ… Backup created')) - } - - console.log(colors.info('πŸ—‘οΈ Clearing all data...')) - await brainyInstance.clear({ force: true }) - console.log(colors.success('βœ… All data cleared successfully')) - })) - -// Command 8: EXPORT - Export your data -program - .command('export') - .description('Export your brain data in various formats') - .option('-f, --format ', 'Export format (json, csv, graph, embeddings)', 'json') - .option('-o, --output ', 'Output file path') - .option('--vectors', 'Include vector embeddings') - .option('--no-metadata', 'Exclude metadata') - .option('--no-relationships', 'Exclude relationships') - .option('--filter ', 'Filter by metadata') - .option('-l, --limit ', 'Limit number of items') - .action(wrapAction(async (options) => { - const brainy = await initBrainy() - console.log(colors.brain('πŸ“€ Exporting Brain Data')) - - const spinner = ora('Exporting data...').start() - - try { - const exportOptions = { - format: options.format, - includeVectors: options.vectors || false, - includeMetadata: options.metadata !== false, - includeRelationships: options.relationships !== false, - filter: options.filter ? JSON.parse(options.filter) : {}, - limit: options.limit ? parseInt(options.limit) : undefined - } - - const data = await brainy.export(exportOptions) - - spinner.succeed('Export complete') - - if (options.output) { - // Write to file - const fs = require('fs') - const content = typeof data === 'string' ? data : JSON.stringify(data, null, 2) - fs.writeFileSync(options.output, content) - console.log(colors.success(`βœ… Exported to: ${options.output}`)) - - // Show summary - const items = Array.isArray(data) ? data.length : (data.nodes ? data.nodes.length : 1) - console.log(colors.info(`πŸ“Š Format: ${options.format}`)) - console.log(colors.info(`πŸ“ Items: ${items}`)) - if (options.vectors) { - console.log(colors.info(`πŸ”’ Vectors: Included`)) - } - } else { - // Output to console - if (typeof data === 'string') { - console.log(data) - } else { - console.log(JSON.stringify(data, null, 2)) - } - } - } catch (error) { - spinner.fail('Export failed') - console.error(colors.error(error.message)) - process.exit(1) - } - })) - -// Command 8: CLOUD - Premium features connection -program - .command('cloud ') - .description('☁️ Brain Cloud - AI Memory, Team Sync, Enterprise Connectors (FREE TRIAL!)') - .option('-i, --instance ', 'Brain Cloud instance ID') - .option('-e, --email ', 'Your email for signup') - .action(wrapAction(async (action, options) => { - console.log(boxen( - colors.brain('☁️ BRAIN CLOUD - SUPERCHARGE YOUR BRAIN! πŸš€\n\n') + - colors.success('✨ FREE TRIAL: First 100GB FREE!\n') + - colors.info('πŸ’° Then just $9/month (individuals) or $49/month (teams)\n\n') + - colors.primary('Features:\n') + - colors.dim(' β€’ AI Memory that persists across sessions\n') + - colors.dim(' β€’ Multi-agent coordination\n') + - colors.dim(' β€’ Automatic backups & sync\n') + - colors.dim(' β€’ Premium connectors (Notion, Slack, etc.)'), - { padding: 1, borderStyle: 'round', borderColor: 'cyan' } - )) - - const cloudActions = { - setup: async () => { - console.log(colors.brain('\nπŸš€ Quick Setup - 30 seconds to superpowers!\n')) - - if (!options.email) { - const { email } = await prompts({ - type: 'text', - name: 'email', - message: 'Enter your email for FREE trial:', - validate: (value) => value.includes('@') || 'Please enter a valid email' - }) - options.email = email - } - - console.log(colors.success(`\nβœ… Setting up Brain Cloud for: ${options.email}`)) - console.log(colors.info('\nπŸ“§ Check your email for activation link!')) - console.log(colors.dim('\nOr visit: https://app.soulcraft.com/activate\n')) - - // Cloud features planned for future release - console.log(colors.brain('πŸŽ‰ Your Brain Cloud trial is ready!')) - console.log(colors.success('\nNext steps:')) - console.log(colors.dim(' 1. Check your email for API key')) - console.log(colors.dim(' 2. Run: brainy cloud connect --key YOUR_KEY')) - console.log(colors.dim(' 3. Start using persistent AI memory!')) - }, - connect: async () => { - console.log(colors.info('πŸ”— Connecting to Brain Cloud...')) - // Dynamic import to avoid loading premium code unnecessarily - console.log(colors.info('πŸ”— Cloud features coming soon...')) - console.log(colors.info('Brainy works offline by default')) - }, - status: async () => { - console.log(colors.info('☁️ Cloud Status: Available in future release')) - console.log(colors.info('Current version works offline')) - }, - augmentations: async () => { - console.log(colors.info('🧩 Cloud augmentations coming in future release')) - console.log(colors.info('Local augmentations available now')) - } - } - - if (cloudActions[action]) { - await cloudActions[action]() - } else { - console.log(colors.error('Valid actions: connect, status, augmentations')) - console.log(colors.info('Example: brainy cloud connect --instance demo-test-auto')) - } - })) - -// Command 7: MIGRATE - Migration tools -program - .command('migrate ') - .description('Migration tools for upgrades') - .option('-f, --from ', 'Migrate from version') - .option('-b, --backup', 'Create backup before migration') - .action(wrapAction(async (action, options) => { - console.log(colors.primary('πŸ”„ Brainy Migration Tools')) - - const migrateActions = { - check: async () => { - console.log(colors.info('πŸ” Checking for migration needs...')) - // Check for deprecated methods, old config, etc. - const issues = [] - - try { - const { BrainyData } = await import('../dist/brainyData.js') - const brainy = new BrainyData() - - // Check for old API usage - console.log(colors.success('βœ… No migration issues found')) - } catch (error) { - console.log(colors.warning(`⚠️ Found issues: ${error.message}`)) - } - }, - backup: async () => { - console.log(colors.info('πŸ’Ύ Creating backup...')) - const { BrainyData } = await import('../dist/brainyData.js') - const brainy = new BrainyData() - const backup = await brainy.createBackup() - console.log(colors.success(`βœ… Backup created: ${backup.path}`)) - }, - restore: async () => { - if (!options.from) { - console.error(colors.error('Please specify backup file: --from ')) - process.exit(1) - } - console.log(colors.info(`πŸ“₯ Restoring from: ${options.from}`)) - const { BrainyData } = await import('../dist/brainyData.js') - const brainy = new BrainyData() - await brainy.restoreBackup(options.from) - console.log(colors.success('βœ… Restore complete')) - } - } - - if (migrateActions[action]) { - await migrateActions[action]() - } else { - console.log(colors.error('Valid actions: check, backup, restore')) - console.log(colors.info('Example: brainy migrate check')) - } - })) - -// Command 8: HELP - Interactive guidance -program - .command('help [command]') - .description('Get help or enter interactive mode') - .action(wrapAction(async (command) => { - if (command) { - program.help() - return - } - - // Interactive mode for beginners - console.log(colors.primary('πŸ§ βš›οΈ Welcome to Brainy!')) - console.log(colors.info('Your AI-powered second brain')) - console.log() - - const rl = createInterface({ - input: process.stdin, - output: process.stdout - }) - - console.log(colors.primary('What would you like to do?')) - console.log(colors.info('1. Add some data')) - console.log(colors.info('2. Chat with AI using your data')) - console.log(colors.info('3. Search your brain')) - console.log(colors.info('4. Update existing data')) - console.log(colors.info('5. Delete data')) - console.log(colors.info('6. Import a file')) - console.log(colors.info('7. Check status')) - console.log(colors.info('8. Connect to Brain Cloud')) - console.log(colors.info('9. Configuration')) - console.log(colors.info('10. Show all commands')) - console.log() - - const choice = await new Promise(resolve => { - rl.question(colors.primary('Enter your choice (1-10): '), (answer) => { - rl.close() - resolve(answer) - }) - }) - - switch (choice) { - case '1': - console.log(colors.success('\n🧠 Use: brainy add "your data here"')) - console.log(colors.info('Example: brainy add "John works at Google"')) - break - case '2': - console.log(colors.success('\nπŸ’¬ Use: brainy chat "your question"')) - console.log(colors.info('Example: brainy chat "Tell me about my data"')) - console.log(colors.info('Supports: local (Ollama), OpenAI, Claude')) - break - case '3': - console.log(colors.success('\nπŸ” Use: brainy search "your query"')) - console.log(colors.info('Example: brainy search "Google employees"')) - break - case '4': - console.log(colors.success('\nπŸ“₯ Use: brainy import ')) - console.log(colors.info('Example: brainy import data.txt')) - break - case '5': - console.log(colors.success('\nπŸ“Š Use: brainy status')) - console.log(colors.info('Shows comprehensive brain statistics')) - console.log(colors.info('Options: --simple (quick) or --verbose (detailed)')) - break - case '6': - console.log(colors.success('\n☁️ Use: brainy cloud connect')) - console.log(colors.info('Example: brainy cloud connect --instance demo-test-auto')) - break - case '7': - console.log(colors.success('\nπŸ”§ Use: brainy config ')) - console.log(colors.info('Example: brainy config list')) - break - case '8': - program.help() - break - default: - console.log(colors.warning('Invalid choice. Use "brainy --help" for all commands.')) - } - })) - -// ======================================== -// FALLBACK - Show interactive help if no command -// ======================================== - -// Handle --interactive flag -if (process.argv.includes('-i') || process.argv.includes('--interactive')) { - // Start full interactive mode - console.log(colors.primary('🧠 Starting Interactive Mode...')) - import('./brainy-interactive.js').then(module => { - module.startInteractiveMode() - }).catch(error => { - console.error(colors.error('Failed to start interactive mode:'), error.message) - // Fallback to simple interactive prompt - program.parse(['node', 'brainy', 'help']) - }) -} else if (process.argv.length === 2) { - // No arguments - show interactive help - program.parse(['node', 'brainy', 'help']) -} else { - // Parse normally - program.parse(process.argv) -} \ No newline at end of file +import('../dist/cli/index.js').catch((error) => { + console.error('Failed to load Brainy CLI:', error.message) + console.error('Make sure you have built the project: npm run build') + process.exit(1) +}) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ed76b137..1421d814 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@soulcraft/brainy", - "version": "3.19.0", + "version": "3.19.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@soulcraft/brainy", - "version": "3.19.0", + "version": "3.19.1", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.540.0", diff --git a/package.json b/package.json index bd505190..572b85dc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@soulcraft/brainy", - "version": "3.19.0", + "version": "3.19.1", "description": "Universal Knowledge Protocolβ„’ - World's first Triple Intelligence database unifying vector, graph, and document search in one API. 31 nouns Γ— 40 verbs for infinite expressiveness.", "main": "dist/index.js", "module": "dist/index.js", diff --git a/src/cli/commands/conversation.ts b/src/cli/commands/conversation.ts index 36d901fc..183f4240 100644 --- a/src/cli/commands/conversation.ts +++ b/src/cli/commands/conversation.ts @@ -31,7 +31,7 @@ export const conversationCommand = { .positional('action', { describe: 'Conversation operation to perform', type: 'string', - choices: ['setup', 'search', 'context', 'thread', 'stats', 'export', 'import'] + choices: ['setup', 'remove', 'search', 'context', 'thread', 'stats', 'export', 'import'] }) .option('conversation-id', { describe: 'Conversation ID', @@ -82,6 +82,9 @@ export const conversationCommand = { case 'setup': await handleSetup(argv) break + case 'remove': + await handleRemove(argv) + break case 'search': await handleSearch(argv) break @@ -230,7 +233,15 @@ async function main() { main() ` - await fs.writeFile(serverPath, serverScript, { encoding: 'utf8', mode: 0o755 }) + await fs.writeFile(serverPath, serverScript, 'utf8') + // Make executable on Unix systems + try { + await import('fs').then(fsModule => { + fsModule.promises.chmod(serverPath, 0o755).catch(() => {}) + }) + } catch { + // Windows doesn't need chmod + } spinner.succeed('Created MCP server script') // Create Claude Code config @@ -262,7 +273,9 @@ main() const brain = new Brainy({ storage: { type: 'filesystem', - path: dataDir + options: { + path: dataDir + } }, silent: true }) @@ -289,6 +302,94 @@ main() } } +/** + * Handle remove command - Remove MCP server and optionally data + */ +async function handleRemove(argv: CommandArguments) { + console.log(chalk.bold.cyan('\nπŸ—‘οΈ Brainy Infinite Memory Removal\n')) + + const homeDir = process.env.HOME || process.env.USERPROFILE || '~' + const brainyDir = path.join(homeDir, '.brainy-memory') + const configPath = path.join(homeDir, '.config', 'claude-code', 'mcp-servers.json') + + // Check if setup exists + const brainyExists = await fs.exists(brainyDir) + const configExists = await fs.exists(configPath) + + if (!brainyExists && !configExists) { + console.log(chalk.yellow('No Brainy memory setup found. Nothing to remove.')) + return + } + + // Show what will be removed + console.log(chalk.white('The following will be removed:')) + if (brainyExists) { + console.log(chalk.dim(` β€’ ${brainyDir} (memory data and MCP server)`)) + } + if (configExists) { + console.log(chalk.dim(` β€’ MCP config entry in ${configPath}`)) + } + console.log() + + // Confirm removal + const { confirm } = await inquirer.prompt([ + { + type: 'confirm', + name: 'confirm', + message: 'Are you sure you want to remove Brainy infinite memory?', + default: false + } + ]) + + if (!confirm) { + console.log(chalk.yellow('Removal cancelled')) + return + } + + const spinner = ora('Removing Brainy memory setup...').start() + + try { + // Remove Brainy directory + if (brainyExists) { + // Use Node.js fs for rm operation as universal fs doesn't have it + const nodefs = await import('fs') + await nodefs.promises.rm(brainyDir, { recursive: true, force: true }) + spinner.text = 'Removed memory directory...' + } + + // Remove MCP config entry + if (configExists) { + try { + const configContent = await fs.readFile(configPath, 'utf8') + const mcpConfig = JSON.parse(configContent) + + if (mcpConfig['brainy-memory']) { + delete mcpConfig['brainy-memory'] + await fs.writeFile(configPath, JSON.stringify(mcpConfig, null, 2), 'utf8') + spinner.text = 'Removed MCP configuration...' + } + } catch (error) { + // If config file is corrupted or empty, skip + spinner.warn('Could not update MCP config file') + } + } + + spinner.succeed('Successfully removed Brainy infinite memory') + + console.log() + console.log(chalk.bold('βœ… Cleanup complete')) + console.log() + console.log(chalk.white('All conversation data and MCP configuration have been removed.')) + console.log(chalk.dim('Run "brainy conversation setup" to set up again.')) + console.log() + + } catch (error: any) { + spinner.fail('Removal failed') + console.error(chalk.red('Error:'), error.message) + throw error + } +} + /** * Handle search command - Search messages */ diff --git a/src/cli/index.ts b/src/cli/index.ts index 1e15f218..662f04dc 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -14,7 +14,13 @@ import { neuralCommands } from './commands/neural.js' import { coreCommands } from './commands/core.js' import { utilityCommands } from './commands/utility.js' import conversationCommand from './commands/conversation.js' -import { version } from '../package.json' +import { readFileSync } from 'fs' +import { fileURLToPath } from 'url' +import { dirname, join } from 'path' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const packageJson = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf8')) +const version = packageJson.version // CLI Configuration const program = new Command() diff --git a/tsconfig.json b/tsconfig.json index 4ea25e14..737ea9bc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -28,8 +28,6 @@ "node_modules", "dist", "**/*.test.ts", - "src/cli/**/*", - "src/scripts/**/*", - "src/mcp/**/*" + "src/scripts/**/*" ] } \ No newline at end of file