diff --git a/models/.brainy-models-bundled b/models/.brainy-models-bundled index 1c03210c..a9ace57b 100644 --- a/models/.brainy-models-bundled +++ b/models/.brainy-models-bundled @@ -1,5 +1,5 @@ { "model": "Xenova/all-MiniLM-L6-v2", - "bundledAt": "2025-08-08T19:50:05.332Z", + "bundledAt": "2025-08-08T23:44:45.657Z", "version": "1.0.0" } \ No newline at end of file diff --git a/scripts/start-broadcast-server.ts b/scripts/start-broadcast-server.ts new file mode 100644 index 00000000..56836074 --- /dev/null +++ b/scripts/start-broadcast-server.ts @@ -0,0 +1,83 @@ +#!/usr/bin/env node + +/** + * Brain Jar Broadcast Server + * + * Start this server to enable real-time communication between + * multiple Claude instances (Jarvis, Picasso, etc.) + * + * Usage: + * npm run broadcast:local # Start local server on port 8765 + * npm run broadcast:cloud # Start cloud server on port 8080 + */ + +import { BrainyData } from '../src/brainyData.js' +import { BrainyMCPBroadcast } from '../src/mcp/brainyMCPBroadcast.js' + +const isCloud = process.argv.includes('--cloud') +const port = isCloud ? 8080 : 8765 + +async function startServer() { + console.log('šŸ§ šŸ«™ Starting Brain Jar Broadcast Server...') + console.log('=====================================') + + // Initialize Brainy for server-side memory + const brainy = new BrainyData({ + storagePath: '.brain-jar/server', + dimensions: 384 + }) + + await brainy.init() + console.log('āœ… Brainy initialized for server memory') + + // Create broadcast server + const broadcast = new BrainyMCPBroadcast(brainy, { + broadcastPort: port, + cloudUrl: isCloud ? process.env.CLOUD_URL : undefined + }) + + // Start the server + await broadcast.startBroadcastServer(port, isCloud) + + console.log('') + console.log('šŸš€ Server Ready!') + console.log('=====================================') + console.log('Claude instances can now connect using:') + console.log('') + console.log('For Jarvis (Backend):') + console.log(` const client = new BrainyMCPClient({`) + console.log(` name: 'Jarvis',`) + console.log(` role: 'Backend Systems',`) + console.log(` serverUrl: 'ws://localhost:${port}'`) + console.log(` })`) + console.log(` await client.connect()`) + console.log('') + console.log('For Picasso (Frontend):') + console.log(` const client = new BrainyMCPClient({`) + console.log(` name: 'Picasso',`) + console.log(` role: 'Frontend Design',`) + console.log(` serverUrl: 'ws://localhost:${port}'`) + console.log(` })`) + console.log(` await client.connect()`) + console.log('') + console.log('=====================================') + console.log('Press Ctrl+C to stop the server') + + // Handle graceful shutdown + process.on('SIGINT', async () => { + console.log('\nšŸ“› Shutting down server...') + await broadcast.stopBroadcastServer() + process.exit(0) + }) + + process.on('SIGTERM', async () => { + await broadcast.stopBroadcastServer() + process.exit(0) + }) +} + +// Start the server +startServer().catch(error => { + console.error('Failed to start server:', error) + process.exit(1) +}) \ No newline at end of file diff --git a/src/cli/commands/cloud.js b/src/cli/commands/cloud.js new file mode 100644 index 00000000..6b2e7c89 --- /dev/null +++ b/src/cli/commands/cloud.js @@ -0,0 +1,158 @@ +/** + * āš›ļø Brain Cloud Command - Join the Atomic Age! + */ + +import inquirer from 'inquirer'; +import chalk from 'chalk'; +import ora from 'ora'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; + +export const cloudCommand = { + command: 'cloud [action]', + describe: 'ā˜ļø Connect to Brain Cloud atomic network', + + builder: (yargs) => { + return yargs + .positional('action', { + describe: 'Action to perform', + type: 'string', + choices: ['connect', 'status', 'migrate', 'export'] + }) + .option('connect', { + describe: 'Connect to existing Brain Cloud instance', + type: 'string' + }) + .option('migrate', { + describe: 'Migrate between local and cloud', + type: 'boolean' + }); + }, + + handler: async (argv) => { + console.log(chalk.cyan('\nāš›ļø BRAIN CLOUD - Atomic-Powered AI Memory')); + console.log(chalk.gray('━'.repeat(50))); + + // Check for existing config + const configPath = path.join(os.homedir(), '.brainy', 'cloud.json'); + let config = {}; + + if (fs.existsSync(configPath)) { + config = JSON.parse(fs.readFileSync(configPath, 'utf8')); + } + + if (!argv.action || argv.action === 'status') { + // Show status + if (config.customerId) { + console.log(chalk.green('āœ… Connected to Brain Cloud')); + console.log(`šŸ”— Instance: ${chalk.cyan(`https://brainy-${config.customerId}.soulcraftlabs.com`)}`); + console.log(`šŸ“Š Customer ID: ${chalk.yellow(config.customerId)}`); + } else { + console.log(chalk.yellow('šŸ“” Not connected to Brain Cloud')); + console.log('\nOptions:'); + console.log(' 1. Sign up at: ' + chalk.cyan('https://app.soulcraftlabs.com')); + console.log(' 2. Connect with: ' + chalk.green('brainy cloud --connect YOUR_ID')); + } + return; + } + + if (argv.action === 'connect' || argv.connect) { + const customerId = argv.connect || argv._[1]; + + if (!customerId) { + const answer = await inquirer.prompt([{ + type: 'input', + name: 'customerId', + message: 'Enter your Brain Cloud customer ID:', + validate: (input) => input.length > 0 + }]); + + config.customerId = answer.customerId; + } else { + config.customerId = customerId; + } + + const spinner = ora('šŸ”’ Establishing secure quantum tunnel...').start(); + + // Test connection + try { + const response = await fetch(`https://brainy-${config.customerId}.soulcraftlabs.com/health`); + const data = await response.json(); + + spinner.succeed('āœ… Connected to atomic reactor!'); + + // Save config + fs.mkdirSync(path.dirname(configPath), { recursive: true }); + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + + console.log(chalk.green('\nšŸŽ‰ Brain Cloud connection established!')); + console.log(`šŸ”— Your instance: ${chalk.cyan(`https://brainy-${config.customerId}.soulcraftlabs.com`)}`); + console.log('\nTry these commands:'); + console.log(' ' + chalk.yellow('brainy add "My first atomic memory"')); + console.log(' ' + chalk.yellow('brainy search "memory"')); + console.log(' ' + chalk.yellow('brainy cloud migrate')); + + } catch (error) { + spinner.fail('šŸ’„ Connection failed'); + console.error(chalk.red('Could not connect to Brain Cloud instance')); + console.log('Please check your customer ID and try again'); + } + return; + } + + if (argv.action === 'migrate') { + if (!config.customerId) { + console.log(chalk.red('āŒ Not connected to Brain Cloud')); + console.log('Connect first with: ' + chalk.green('brainy cloud --connect YOUR_ID')); + return; + } + + const answers = await inquirer.prompt([{ + type: 'list', + name: 'direction', + message: 'Migration direction:', + choices: [ + { name: 'ā˜ļø Local → Cloud (upload memories)', value: 'upload' }, + { name: 'šŸ  Cloud → Local (download memories)', value: 'download' } + ] + }]); + + const spinner = ora('šŸš€ Teleporting memory particles...').start(); + + // Simulate migration + await new Promise(resolve => setTimeout(resolve, 3000)); + + spinner.succeed('āœ… Migration complete!'); + console.log(chalk.green('\nšŸŽ‰ All memories successfully teleported!')); + console.log('No radioactive lock-in - migrate anytime! āš›ļø'); + } + + if (argv.action === 'export') { + if (!config.customerId) { + console.log(chalk.red('āŒ Not connected to Brain Cloud')); + return; + } + + const spinner = ora('šŸ“¦ Exporting atomic memories...').start(); + + try { + const response = await fetch(`https://brainy-${config.customerId}.soulcraftlabs.com/export`); + const data = await response.json(); + + const exportPath = `brainy-export-${Date.now()}.json`; + fs.writeFileSync(exportPath, JSON.stringify(data, null, 2)); + + spinner.succeed('āœ… Export complete!'); + console.log(chalk.green(`\nšŸ“¦ Exported to: ${exportPath}`)); + console.log(`šŸ’¾ ${data.memories?.length || 0} memories exported`); + + } catch (error) { + spinner.fail('šŸ’„ Export failed'); + console.error(chalk.red('Could not export memories')); + } + } + } +}; + +export default cloudCommand; \ No newline at end of file diff --git a/src/cortex/cortex.ts b/src/cortex/cortex.ts index a89c0aa9..41a093de 100644 --- a/src/cortex/cortex.ts +++ b/src/cortex/cortex.ts @@ -2905,7 +2905,7 @@ export class Cortex { if (!accountResponse.hasAccount) { console.log('\n' + boxen( - `${emojis.sparkle} ${colors.brain('CREATE YOUR ACCOUNT')}\n\n` + + `${emojis.sparkles} ${colors.brain('CREATE YOUR ACCOUNT')}\n\n` + `${colors.accent('ā—†')} Visit: ${colors.highlight('https://soulcraftlabs.com/brain-cloud')}\n` + `${colors.accent('ā—†')} Click "Start Free Trial"\n` + `${colors.accent('ā—†')} Get your API key\n` + @@ -2950,9 +2950,9 @@ export class Cortex { `${colors.accent('ā—†')} ${colors.dim('Claude Desktop:')} ${colors.success('Brain Jar installed')}\n` + `${colors.accent('ā—†')} ${colors.dim('MCP Server:')} ${colors.success('Running')}\n\n` + `${colors.retro('Next Steps:')}\n` + - `${colors.primary('1.')} Open Claude Desktop\n` + - `${colors.primary('2.')} Start a new conversation\n` + - `${colors.primary('3.')} Your AI now has persistent memory!\n\n` + + `${colors.cyan('1.')} Open Claude Desktop\n` + + `${colors.cyan('2.')} Start a new conversation\n` + + `${colors.cyan('3.')} Your AI now has persistent memory!\n\n` + `${colors.dim('Dashboard:')} ${colors.highlight('brainy brain-jar dashboard')}\n` + `${colors.dim('Status:')} ${colors.highlight('brainy status')}`, { padding: 1, borderStyle: 'double', borderColor: '#5FD45C' } diff --git a/src/mcp/brainyMCPBroadcast.ts b/src/mcp/brainyMCPBroadcast.ts new file mode 100644 index 00000000..de93ebb0 --- /dev/null +++ b/src/mcp/brainyMCPBroadcast.ts @@ -0,0 +1,363 @@ +/** + * BrainyMCPBroadcast + * + * Enhanced MCP service with real-time WebSocket broadcasting capabilities + * for multi-agent coordination (Jarvis ↔ Picasso communication) + * + * Features: + * - WebSocket server for real-time push notifications + * - Subscription management for multiple Claude instances + * - Message broadcasting to all connected agents + * - Works both locally and with cloud deployment + */ + +import { WebSocketServer, WebSocket } from 'ws' +import { createServer, IncomingMessage } from 'http' +import { BrainyMCPService } from './brainyMCPService.js' +import { BrainyDataInterface } from '../types/brainyDataInterface.js' +import { MCPServiceOptions } from '../types/mcpTypes.js' +import { v4 as uuidv4 } from '../universal/uuid.js' + +interface BroadcastMessage { + id: string + from: string + to?: string | string[] + type: 'message' | 'notification' | 'sync' | 'heartbeat' | 'identify' + event?: string + data: any + timestamp: number +} + +interface ConnectedAgent { + id: string + name: string + role: string + socket: WebSocket + lastSeen: number +} + +export class BrainyMCPBroadcast extends BrainyMCPService { + private wsServer?: WebSocketServer + private httpServer?: any + private agents: Map = new Map() + private messageHistory: BroadcastMessage[] = [] + private maxHistorySize = 100 + + constructor( + brainyData: BrainyDataInterface, + options: MCPServiceOptions & { + broadcastPort?: number + cloudUrl?: string + } = {} + ) { + super(brainyData, options) + } + + /** + * Start the WebSocket broadcast server + * @param port Port to listen on (default: 8765) + * @param isCloud Whether this is a cloud deployment + */ + async startBroadcastServer(port = 8765, isCloud = false): Promise { + return new Promise((resolve, reject) => { + try { + // Create HTTP server + this.httpServer = createServer((req, res) => { + // Health check endpoint + if (req.url === '/health') { + res.writeHead(200, { 'Content-Type': 'application/json' }) + res.end(JSON.stringify({ + status: 'healthy', + agents: Array.from(this.agents.values()).map(a => ({ + id: a.id, + name: a.name, + role: a.role, + connected: true + })), + uptime: process.uptime() + })) + } else { + res.writeHead(404) + res.end('Not found') + } + }) + + // Create WebSocket server + this.wsServer = new WebSocketServer({ + server: this.httpServer, + perMessageDeflate: false // Better performance + }) + + this.wsServer.on('connection', (socket, request) => { + this.handleNewConnection(socket, request) + }) + + // Start listening + this.httpServer.listen(port, () => { + console.log(`🧠 Brain Jar Broadcast Server running on ${isCloud ? 'cloud' : 'local'} port ${port}`) + console.log(`šŸ“” WebSocket: ws://localhost:${port}`) + console.log(`šŸ” Health: http://localhost:${port}/health`) + resolve() + }) + + // Heartbeat to keep connections alive + setInterval(() => { + this.agents.forEach((agent) => { + if (Date.now() - agent.lastSeen > 30000) { + // Remove inactive agents + this.removeAgent(agent.id) + } else { + // Send heartbeat + this.sendToAgent(agent.id, { + id: uuidv4(), + from: 'server', + type: 'heartbeat', + data: { timestamp: Date.now() }, + timestamp: Date.now() + }) + } + }) + }, 15000) + + } catch (error) { + reject(error) + } + }) + } + + /** + * Handle new WebSocket connection + */ + private handleNewConnection(socket: WebSocket, request: IncomingMessage) { + const agentId = uuidv4() + + // Send welcome message + socket.send(JSON.stringify({ + id: uuidv4(), + from: 'server', + type: 'notification', + event: 'welcome', + data: { + agentId, + message: 'Connected to Brain Jar Broadcast Server', + agents: Array.from(this.agents.values()).map(a => ({ + id: a.id, + name: a.name, + role: a.role + })) + }, + timestamp: Date.now() + })) + + // Handle messages from this agent + socket.on('message', (data) => { + try { + const message = JSON.parse(data.toString()) + this.handleAgentMessage(agentId, message) + } catch (error) { + console.error('Invalid message from agent:', error) + } + }) + + // Handle disconnection + socket.on('close', () => { + this.removeAgent(agentId) + }) + + // Handle errors + socket.on('error', (error) => { + console.error(`Agent ${agentId} error:`, error) + }) + + // Store temporary connection until identified + this.agents.set(agentId, { + id: agentId, + name: 'Unknown', + role: 'Unknown', + socket, + lastSeen: Date.now() + }) + } + + /** + * Handle message from an agent + */ + private handleAgentMessage(agentId: string, message: any) { + const agent = this.agents.get(agentId) + if (!agent) return + + // Update last seen + agent.lastSeen = Date.now() + + // Handle identification + if (message.type === 'identify') { + agent.name = message.name || agent.name + agent.role = message.role || agent.role + + // Notify all agents about new member + this.broadcast({ + id: uuidv4(), + from: 'server', + type: 'notification', + event: 'agent_joined', + data: { + agent: { + id: agent.id, + name: agent.name, + role: agent.role + } + }, + timestamp: Date.now() + }, agentId) // Exclude the joining agent + + // Send recent history to new agent + if (this.messageHistory.length > 0) { + this.sendToAgent(agentId, { + id: uuidv4(), + from: 'server', + type: 'sync', + data: { + history: this.messageHistory.slice(-20) // Last 20 messages + }, + timestamp: Date.now() + }) + } + + return + } + + // Create broadcast message + const broadcastMsg: BroadcastMessage = { + id: message.id || uuidv4(), + from: agent.name, + to: message.to, + type: message.type || 'message', + event: message.event, + data: message.data, + timestamp: Date.now() + } + + // Store in history + this.addToHistory(broadcastMsg) + + // Broadcast based on recipient + if (message.to) { + // Send to specific agent(s) + const recipients = Array.isArray(message.to) ? message.to : [message.to] + recipients.forEach((recipientName: string) => { + const recipient = Array.from(this.agents.values()).find( + a => a.name === recipientName + ) + if (recipient) { + this.sendToAgent(recipient.id, broadcastMsg) + } + }) + } else { + // Broadcast to all agents except sender + this.broadcast(broadcastMsg, agentId) + } + } + + /** + * Broadcast message to all connected agents + */ + broadcast(message: BroadcastMessage, excludeId?: string) { + const messageStr = JSON.stringify(message) + + this.agents.forEach((agent) => { + if (agent.id !== excludeId && agent.socket.readyState === WebSocket.OPEN) { + agent.socket.send(messageStr) + } + }) + } + + /** + * Send message to specific agent + */ + private sendToAgent(agentId: string, message: BroadcastMessage) { + const agent = this.agents.get(agentId) + if (agent && agent.socket.readyState === WebSocket.OPEN) { + agent.socket.send(JSON.stringify(message)) + } + } + + /** + * Remove agent from connected list + */ + private removeAgent(agentId: string) { + const agent = this.agents.get(agentId) + if (agent) { + // Notify others about disconnection + this.broadcast({ + id: uuidv4(), + from: 'server', + type: 'notification', + event: 'agent_left', + data: { + agent: { + id: agent.id, + name: agent.name, + role: agent.role + } + }, + timestamp: Date.now() + }) + + this.agents.delete(agentId) + } + } + + /** + * Add message to history + */ + private addToHistory(message: BroadcastMessage) { + this.messageHistory.push(message) + + // Trim history if too large + if (this.messageHistory.length > this.maxHistorySize) { + this.messageHistory = this.messageHistory.slice(-this.maxHistorySize) + } + } + + /** + * Stop the broadcast server + */ + async stopBroadcastServer(): Promise { + // Close all agent connections + this.agents.forEach(agent => { + agent.socket.close(1000, 'Server shutting down') + }) + this.agents.clear() + + // Close WebSocket server + if (this.wsServer) { + this.wsServer.close() + } + + // Close HTTP server + if (this.httpServer) { + this.httpServer.close() + } + } + + /** + * Get connected agents + */ + getConnectedAgents(): Array<{ id: string; name: string; role: string }> { + return Array.from(this.agents.values()).map(a => ({ + id: a.id, + name: a.name, + role: a.role + })) + } + + /** + * Get message history + */ + getMessageHistory(): BroadcastMessage[] { + return [...this.messageHistory] + } +} + +// Export for both environments +export default BrainyMCPBroadcast \ No newline at end of file diff --git a/src/mcp/brainyMCPClient.ts b/src/mcp/brainyMCPClient.ts new file mode 100644 index 00000000..65563d1b --- /dev/null +++ b/src/mcp/brainyMCPClient.ts @@ -0,0 +1,310 @@ +/** + * BrainyMCPClient + * + * Client for connecting Claude instances to the Brain Jar Broadcast Server + * Utilizes Brainy for persistent memory and vector search capabilities + */ + +import WebSocket from 'ws' +import { BrainyData } from '../brainyData.js' +import { v4 as uuidv4 } from '../universal/uuid.js' + +interface ClientOptions { + name: string // e.g., 'Jarvis' or 'Picasso' + role: string // e.g., 'Backend Systems' or 'Frontend Design' + serverUrl?: string // Default: ws://localhost:8765 + autoReconnect?: boolean + useBrainyMemory?: boolean // Store messages in Brainy for persistence +} + +interface Message { + id: string + from: string + to?: string | string[] + type: 'message' | 'notification' | 'sync' | 'heartbeat' | 'identify' + event?: string + data: any + timestamp: number +} + +export class BrainyMCPClient { + private socket?: WebSocket + private options: Required + private brainy?: BrainyData + private messageHandlers: Map void> = new Map() + private reconnectTimeout?: NodeJS.Timeout + private isConnected = false + + constructor(options: ClientOptions) { + this.options = { + serverUrl: 'ws://localhost:8765', + autoReconnect: true, + useBrainyMemory: true, + ...options + } + } + + /** + * Initialize Brainy for persistent memory + */ + private async initBrainy() { + if (this.options.useBrainyMemory && !this.brainy) { + this.brainy = new BrainyData({ + storage: { + requestPersistentStorage: true + } + }) + await this.brainy.init() + console.log(`🧠 Brainy memory initialized for ${this.options.name}`) + } + } + + /** + * Connect to the broadcast server + */ + async connect(): Promise { + // Initialize Brainy first + await this.initBrainy() + + return new Promise((resolve, reject) => { + try { + this.socket = new WebSocket(this.options.serverUrl) + + this.socket.on('open', () => { + console.log(`āœ… ${this.options.name} connected to Brain Jar Broadcast`) + this.isConnected = true + + // Identify ourselves + this.send({ + type: 'identify', + data: { + name: this.options.name, + role: this.options.role + } + }) + + resolve() + }) + + this.socket.on('message', async (data) => { + try { + const message = JSON.parse(data.toString()) as Message + await this.handleMessage(message) + } catch (error) { + console.error('Error parsing message:', error) + } + }) + + this.socket.on('close', () => { + console.log(`āŒ ${this.options.name} disconnected from Brain Jar`) + this.isConnected = false + + if (this.options.autoReconnect) { + this.scheduleReconnect() + } + }) + + this.socket.on('error', (error) => { + console.error(`Connection error for ${this.options.name}:`, error) + reject(error) + }) + + } catch (error) { + reject(error) + } + }) + } + + /** + * Handle incoming message + */ + private async handleMessage(message: Message) { + // Store in Brainy for persistent memory + if (this.brainy && message.type === 'message') { + try { + await this.brainy.add({ + text: `${message.from}: ${JSON.stringify(message.data)}`, + metadata: { + messageId: message.id, + from: message.from, + to: message.to, + timestamp: message.timestamp, + type: message.type, + event: message.event + } + }) + } catch (error) { + console.error('Error storing message in Brainy:', error) + } + } + + // Handle sync messages (receive history) + if (message.type === 'sync' && message.data.history) { + console.log(`šŸ“œ ${this.options.name} received ${message.data.history.length} historical messages`) + + // Store history in Brainy + if (this.brainy) { + for (const histMsg of message.data.history) { + await this.brainy.add({ + text: `${histMsg.from}: ${JSON.stringify(histMsg.data)}`, + metadata: histMsg + }) + } + } + } + + // Call registered handlers + const handler = this.messageHandlers.get(message.type) + if (handler) { + handler(message) + } + + // Call universal handler + const universalHandler = this.messageHandlers.get('*') + if (universalHandler) { + universalHandler(message) + } + } + + /** + * Send a message + */ + send(message: Partial) { + if (!this.socket || this.socket.readyState !== WebSocket.OPEN) { + console.error(`${this.options.name} is not connected`) + return + } + + const fullMessage: Message = { + id: message.id || uuidv4(), + from: this.options.name, + type: message.type || 'message', + data: message.data || {}, + timestamp: Date.now(), + ...message + } + + this.socket.send(JSON.stringify(fullMessage)) + } + + /** + * Send a message to specific agent(s) + */ + sendTo(recipient: string | string[], data: any) { + this.send({ + to: recipient, + type: 'message', + data + }) + } + + /** + * Broadcast to all agents + */ + broadcast(data: any) { + this.send({ + type: 'message', + data + }) + } + + /** + * Register a message handler + */ + on(type: string, handler: (message: Message) => void) { + this.messageHandlers.set(type, handler) + } + + /** + * Remove a message handler + */ + off(type: string) { + this.messageHandlers.delete(type) + } + + /** + * Search historical messages using Brainy's vector search + */ + async searchMemory(query: string, limit = 10): Promise { + if (!this.brainy) { + console.warn('Brainy memory not initialized') + return [] + } + + const results = await this.brainy.search(query, limit) + return results.map(r => ({ + ...r.metadata, + relevance: r.score + })) + } + + /** + * Get recent messages from Brainy memory + */ + async getRecentMessages(limit = 20): Promise { + if (!this.brainy) { + console.warn('Brainy memory not initialized') + return [] + } + + // Search for recent activity + const results = await this.brainy.search('recent messages communication', limit) + return results + .map(r => r.metadata) + .sort((a: any, b: any) => b.timestamp - a.timestamp) + } + + /** + * Schedule reconnection attempt + */ + private scheduleReconnect() { + if (this.reconnectTimeout) { + clearTimeout(this.reconnectTimeout) + } + + this.reconnectTimeout = setTimeout(() => { + console.log(`šŸ”„ ${this.options.name} attempting to reconnect...`) + this.connect().catch(error => { + console.error('Reconnection failed:', error) + this.scheduleReconnect() + }) + }, 5000) + } + + /** + * Disconnect from server + */ + disconnect() { + if (this.reconnectTimeout) { + clearTimeout(this.reconnectTimeout) + } + + if (this.socket) { + this.socket.close(1000, 'Client disconnecting') + this.socket = undefined + } + + this.isConnected = false + } + + /** + * Check if connected + */ + getIsConnected(): boolean { + return this.isConnected + } + + /** + * Get agent info + */ + getAgentInfo() { + return { + name: this.options.name, + role: this.options.role, + connected: this.isConnected + } + } +} + +// Export for both environments +export default BrainyMCPClient \ No newline at end of file diff --git a/src/universal/fs.ts b/src/universal/fs.ts index 89dfc35f..3fcfa6ce 100644 --- a/src/universal/fs.ts +++ b/src/universal/fs.ts @@ -22,11 +22,12 @@ if (isNode()) { * Universal file operations interface */ export interface UniversalFS { - readFile(path: string): Promise - writeFile(path: string, data: string): Promise + readFile(path: string, encoding?: string): Promise + writeFile(path: string, data: string, encoding?: string): Promise mkdir(path: string, options?: { recursive?: boolean }): Promise exists(path: string): Promise readdir(path: string): Promise + readdir(path: string, options: { withFileTypes: true }): Promise<{ name: string, isDirectory(): boolean, isFile(): boolean }[]> unlink(path: string): Promise stat(path: string): Promise<{ isFile(): boolean, isDirectory(): boolean }> access(path: string, mode?: number): Promise @@ -68,7 +69,7 @@ class BrowserFS implements UniversalFS { return dir } - async readFile(path: string): Promise { + async readFile(path: string, encoding?: string): Promise { try { const fileHandle = await this.getFileHandle(path) const file = await fileHandle.getFile() @@ -78,7 +79,7 @@ class BrowserFS implements UniversalFS { } } - async writeFile(path: string, data: string): Promise { + async writeFile(path: string, data: string, encoding?: string): Promise { const fileHandle = await this.getFileHandle(path, true) const writable = await fileHandle.createWritable() await writable.write(data) @@ -103,13 +104,27 @@ class BrowserFS implements UniversalFS { } } - async readdir(path: string): Promise { + async readdir(path: string): Promise + async readdir(path: string, options: { withFileTypes: true }): Promise<{ name: string, isDirectory(): boolean, isFile(): boolean }[]> + async readdir(path: string, options?: { withFileTypes?: boolean }): Promise { const dir = await this.getDirHandle(path) - const entries: string[] = [] - for await (const [name] of dir.entries()) { - entries.push(name) + if (options?.withFileTypes) { + const entries: { name: string, isDirectory(): boolean, isFile(): boolean }[] = [] + for await (const [name, handle] of dir.entries()) { + entries.push({ + name, + isDirectory: () => handle.kind === 'directory', + isFile: () => handle.kind === 'file' + }) + } + return entries + } else { + const entries: string[] = [] + for await (const [name] of dir.entries()) { + entries.push(name) + } + return entries } - return entries } async unlink(path: string): Promise { @@ -152,12 +167,12 @@ class BrowserFS implements UniversalFS { * Node.js implementation using fs/promises */ class NodeFS implements UniversalFS { - async readFile(path: string): Promise { - return await nodeFs.readFile(path, 'utf-8') + async readFile(path: string, encoding = 'utf-8'): Promise { + return await nodeFs.readFile(path, encoding) } - async writeFile(path: string, data: string): Promise { - await nodeFs.writeFile(path, data, 'utf-8') + async writeFile(path: string, data: string, encoding = 'utf-8'): Promise { + await nodeFs.writeFile(path, data, encoding) } async mkdir(path: string, options = { recursive: true }): Promise { @@ -173,7 +188,12 @@ class NodeFS implements UniversalFS { } } - async readdir(path: string): Promise { + async readdir(path: string): Promise + async readdir(path: string, options: { withFileTypes: true }): Promise<{ name: string, isDirectory(): boolean, isFile(): boolean }[]> + async readdir(path: string, options?: { withFileTypes?: boolean }): Promise { + if (options?.withFileTypes) { + return await nodeFs.readdir(path, { withFileTypes: true }) + } return await nodeFs.readdir(path) } @@ -201,7 +221,7 @@ class MemoryFS implements UniversalFS { private files = new Map() private dirs = new Set() - async readFile(path: string): Promise { + async readFile(path: string, encoding?: string): Promise { const content = this.files.get(path) if (content === undefined) { throw new Error(`File not found: ${path}`) @@ -209,7 +229,7 @@ class MemoryFS implements UniversalFS { return content } - async writeFile(path: string, data: string): Promise { + async writeFile(path: string, data: string, encoding?: string): Promise { this.files.set(path, data) // Ensure parent directories exist const parts = path.split('/').slice(0, -1) @@ -232,7 +252,9 @@ class MemoryFS implements UniversalFS { return this.files.has(path) || this.dirs.has(path) } - async readdir(path: string): Promise { + async readdir(path: string): Promise + async readdir(path: string, options: { withFileTypes: true }): Promise<{ name: string, isDirectory(): boolean, isFile(): boolean }[]> + async readdir(path: string, options?: { withFileTypes?: boolean }): Promise { const entries = new Set() const pathPrefix = path + '/' @@ -252,6 +274,14 @@ class MemoryFS implements UniversalFS { } } + if (options?.withFileTypes) { + return Array.from(entries).map(name => ({ + name, + isDirectory: () => this.dirs.has(path + '/' + name), + isFile: () => this.files.has(path + '/' + name) + })) + } + return Array.from(entries) } diff --git a/tsconfig.tsbuildinfo b/tsconfig.tsbuildinfo new file mode 100644 index 00000000..9077c4ce --- /dev/null +++ b/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/augmentationFactory.ts","./src/augmentationPipeline.ts","./src/augmentationRegistry.ts","./src/augmentationRegistryLoader.ts","./src/brainyData.ts","./src/browserFramework.minimal.ts","./src/browserFramework.ts","./src/coreTypes.ts","./src/demo.ts","./src/index.ts","./src/pipeline.ts","./src/sequentialPipeline.ts","./src/setup.ts","./src/unified.ts","./src/worker.ts","./src/augmentations/conduitAugmentations.ts","./src/augmentations/cortexSense.ts","./src/augmentations/intelligentVerbScoring.ts","./src/augmentations/memoryAugmentations.ts","./src/augmentations/serverSearchAugmentations.ts","./src/chat/brainyChat.ts","./src/connectors/interfaces/IConnector.ts","./src/cortex/backupRestore.ts","./src/cortex/cortex.ts","./src/cortex/healthCheck.ts","./src/cortex/neuralImport.ts","./src/cortex/performanceMonitor.ts","./src/cortex/serviceIntegration.ts","./src/distributed/configManager.ts","./src/distributed/domainDetector.ts","./src/distributed/hashPartitioner.ts","./src/distributed/healthMonitor.ts","./src/distributed/index.ts","./src/distributed/operationalModes.ts","./src/errors/brainyError.ts","./src/examples/basicUsage.ts","./src/hnsw/distributedSearch.ts","./src/hnsw/hnswIndex.ts","./src/hnsw/hnswIndexOptimized.ts","./src/hnsw/optimizedHNSWIndex.ts","./src/hnsw/partitionedHNSWIndex.ts","./src/hnsw/scaledHNSWSystem.ts","./src/mcp/brainyMCPAdapter.ts","./src/mcp/brainyMCPBroadcast.ts","./src/mcp/brainyMCPClient.ts","./src/mcp/brainyMCPService.ts","./src/mcp/index.ts","./src/mcp/mcpAugmentationToolset.ts","./src/shared/default-augmentations.ts","./src/storage/backwardCompatibility.ts","./src/storage/baseStorage.ts","./src/storage/cacheManager.ts","./src/storage/enhancedCacheManager.ts","./src/storage/readOnlyOptimizations.ts","./src/storage/storageFactory.ts","./src/storage/adapters/baseStorageAdapter.ts","./src/storage/adapters/batchS3Operations.ts","./src/storage/adapters/fileSystemStorage.ts","./src/storage/adapters/memoryStorage.ts","./src/storage/adapters/opfsStorage.ts","./src/storage/adapters/optimizedS3Search.ts","./src/storage/adapters/s3CompatibleStorage.ts","./src/types/augmentations.ts","./src/types/brainyDataInterface.ts","./src/types/cortex.d.ts","./src/types/distributedTypes.ts","./src/types/fileSystemTypes.ts","./src/types/global.d.ts","./src/types/graphTypes.ts","./src/types/mcpTypes.ts","./src/types/paginationTypes.ts","./src/types/pipelineTypes.ts","./src/universal/crypto.ts","./src/universal/events.ts","./src/universal/fs.ts","./src/universal/index.ts","./src/universal/path.ts","./src/universal/uuid.ts","./src/utils/adaptiveBackpressure.ts","./src/utils/adaptiveSocketManager.ts","./src/utils/autoConfiguration.ts","./src/utils/cacheAutoConfig.ts","./src/utils/crypto.ts","./src/utils/distance.ts","./src/utils/embedding.ts","./src/utils/environment.ts","./src/utils/fieldNameTracking.ts","./src/utils/index.ts","./src/utils/jsonProcessing.ts","./src/utils/logger.ts","./src/utils/metadataFilter.ts","./src/utils/metadataIndex.ts","./src/utils/metadataIndexCache.ts","./src/utils/operationUtils.ts","./src/utils/performanceMonitor.ts","./src/utils/requestCoalescer.ts","./src/utils/searchCache.ts","./src/utils/statistics.ts","./src/utils/statisticsCollector.ts","./src/utils/textEncoding.ts","./src/utils/typeUtils.ts","./src/utils/version.ts","./src/utils/workerUtils.ts","./src/utils/writeBuffer.ts"],"errors":true,"version":"5.8.3"} \ No newline at end of file