UNPKG

jay-code

Version:

Streamlined AI CLI orchestration engine with mathematical rigor and enterprise-grade reliability

670 lines (565 loc) 17.3 kB
/** * Main Console Application * Coordinates all components of the Claude Code Console */ import { WebSocketClient } from './websocket-client.js'; import { TerminalEmulator } from './terminal-emulator.js'; import { CommandHandler } from './command-handler.js'; import { SettingsManager } from './settings.js'; class ClaudeCodeConsole { constructor() { // Initialize components this.wsClient = new WebSocketClient(); this.terminal = null; this.commandHandler = null; this.settings = new SettingsManager(); // State management this.isInitialized = false; this.startTime = Date.now(); this.messageCount = 0; this.activeAgents = 0; // DOM elements this.elements = {}; // Status update intervals this.statusInterval = null; this.uptimeInterval = null; this.setupEventListeners(); } /** * Initialize the console application */ async init() { if (this.isInitialized) return; try { // Show loading overlay this.showLoading('Initializing Claude Code Console...'); // Get DOM elements this.getDOMElements(); // Initialize terminal emulator this.terminal = new TerminalEmulator(this.elements.consoleOutput, this.elements.consoleInput); // Initialize command handler this.commandHandler = new CommandHandler(this.terminal, this.wsClient); // Initialize settings this.settings.init(); // Setup component interactions this.setupComponentInteractions(); // Setup UI event handlers this.setupUIEventHandlers(); // Apply initial settings this.applyInitialSettings(); // Start status updates this.startStatusUpdates(); // Hide loading overlay this.hideLoading(); // Show welcome message this.showWelcomeMessage(); // Auto-connect if enabled if (this.settings.get('autoConnect')) { await this.autoConnect(); } this.isInitialized = true; console.log('Claude Code Console initialized successfully'); } catch (error) { console.error('Failed to initialize console:', error); this.showError('Failed to initialize console: ' + error.message); } } /** * Get DOM elements */ getDOMElements() { this.elements = { consoleOutput: document.getElementById('consoleOutput'), consoleInput: document.getElementById('consoleInput'), settingsPanel: document.getElementById('settingsPanel'), loadingOverlay: document.getElementById('loadingOverlay'), connectionStatus: document.getElementById('connectionStatus'), statusIndicator: document.getElementById('statusIndicator'), statusText: document.getElementById('statusText'), currentMode: document.getElementById('currentMode'), activeAgents: document.getElementById('activeAgents'), uptime: document.getElementById('uptime'), memoryUsage: document.getElementById('memoryUsage'), messageCount: document.getElementById('messageCount'), timestamp: document.getElementById('timestamp'), clearConsole: document.getElementById('clearConsole'), fullscreenToggle: document.getElementById('fullscreenToggle'), }; // Validate required elements const required = ['consoleOutput', 'consoleInput', 'loadingOverlay']; for (const elementId of required) { if (!this.elements[elementId]) { throw new Error(`Required element not found: ${elementId}`); } } } /** * Setup component interactions */ setupComponentInteractions() { // Terminal -> Command Handler this.terminal.on('command', (command) => { this.commandHandler.processCommand(command); }); this.terminal.on('interrupt', () => { this.handleInterrupt(); }); // WebSocket -> Terminal this.wsClient.on('connected', () => { this.updateConnectionStatus(true, false); this.terminal.writeSuccess('Connected to Claude Code server'); this.terminal.setPrompt('jay-code>'); }); this.wsClient.on('disconnected', (info) => { this.updateConnectionStatus(false, false); this.terminal.writeWarning('Disconnected from server'); this.terminal.setPrompt('offline>'); if (info && info.code !== 1000) { this.terminal.writeError(`Connection lost: ${info.reason || 'Unknown reason'}`); } }); this.wsClient.on('reconnecting', (info) => { this.updateConnectionStatus(false, true); this.terminal.writeInfo( `Reconnecting... (${info.attempt}/${this.wsClient.maxReconnectAttempts})`, ); }); this.wsClient.on('error', (error) => { this.terminal.writeError(`WebSocket error: ${error.message || 'Unknown error'}`); }); this.wsClient.on('message_received', (message) => { this.messageCount++; this.handleIncomingMessage(message); }); this.wsClient.on('notification', (notification) => { this.handleNotification(notification); }); // Settings -> Application this.settings.on('connect_requested', async (config) => { await this.connect(config.url, config.token); }); this.settings.on('disconnect_requested', () => { this.disconnect(); }); this.settings.on('max_lines_changed', (maxLines) => { this.terminal.setMaxLines(maxLines); }); this.settings.on('setting_changed', ({ key, value }) => { this.handleSettingChange(key, value); }); } /** * Setup UI event handlers */ setupUIEventHandlers() { // Clear console button if (this.elements.clearConsole) { this.elements.clearConsole.addEventListener('click', () => { this.terminal.clear(); }); } // Fullscreen toggle if (this.elements.fullscreenToggle) { this.elements.fullscreenToggle.addEventListener('click', () => { this.toggleFullscreen(); }); } // Focus input when clicking on output area if (this.elements.consoleOutput) { this.elements.consoleOutput.addEventListener('click', () => { this.terminal.focus(); }); } // Handle window focus window.addEventListener('focus', () => { this.terminal.focus(); }); // Handle visibility change document.addEventListener('visibilitychange', () => { if (!document.hidden) { this.updateTimestamp(); } }); // Handle page unload window.addEventListener('beforeunload', () => { this.cleanup(); }); } /** * Apply initial settings */ applyInitialSettings() { const maxLines = this.settings.get('maxLines'); if (maxLines) { this.terminal.setMaxLines(maxLines); } // Update connection status in settings this.settings.updateConnectionStatus(this.wsClient.getStatus()); } /** * Show welcome message */ showWelcomeMessage() { this.terminal.showWelcomeMessage(); this.terminal.writeInfo('Console ready. Type "help" for available commands.'); const config = this.settings.getConnectionConfig(); if (config.url && !config.autoConnect) { this.terminal.writeInfo(`Use "connect" to connect to ${config.url}`); } } /** * Auto-connect to server */ async autoConnect() { const config = this.settings.getConnectionConfig(); if (config.url) { this.terminal.writeInfo(`Auto-connecting to ${config.url}...`); await this.connect(config.url, config.token); } } /** * Connect to server */ async connect(url, token = '') { try { this.updateConnectionStatus(false, true); await this.wsClient.connect(url, token); await this.wsClient.initializeSession(); // Update settings with successful connection this.settings.set('serverUrl', url); if (token) { this.settings.set('authToken', token); } } catch (error) { this.updateConnectionStatus(false, false); this.terminal.writeError(`Connection failed: ${error.message}`); } } /** * Disconnect from server */ disconnect() { this.wsClient.disconnect(); this.updateConnectionStatus(false, false); } /** * Update connection status in UI */ updateConnectionStatus(connected, connecting) { const status = this.wsClient.getStatus(); // Update status indicator if (this.elements.statusIndicator) { this.elements.statusIndicator.className = 'status-indicator ' + (connected ? 'connected' : connecting ? 'connecting' : ''); } // Update status text if (this.elements.statusText) { this.elements.statusText.textContent = connected ? 'Connected' : connecting ? 'Connecting...' : 'Disconnected'; } // Update settings panel this.settings.updateConnectionStatus(status); } /** * Handle incoming messages */ handleIncomingMessage(message) { // Handle streaming output if (message.method === 'output/stream') { this.handleStreamingOutput(message.params); } // Handle Claude Flow notifications if (message.method && message.method.startsWith('jay-code/')) { this.handleClaudeFlowNotification(message); } } /** * Handle notifications */ handleNotification(notification) { const { method, params } = notification; switch (method) { case 'agent/status': this.handleAgentStatus(params); break; case 'swarm/update': this.handleSwarmUpdate(params); break; case 'memory/update': this.handleMemoryUpdate(params); break; case 'log/message': this.handleLogMessage(params); break; case 'connection/established': this.handleConnectionEstablished(params); break; default: console.log('Unhandled notification:', method, params); } } /** * Handle streaming output */ handleStreamingOutput(params) { if (params && params.content) { const type = params.type || 'output'; if (params.streaming) { // Use streaming text effect for long outputs this.terminal.streamText(params.content, 10); } else { this.terminal.write(params.content, type); } } } /** * Handle Claude Flow notifications */ handleClaudeFlowNotification(message) { const { method, params } = message; switch (method) { case 'jay-code/started': this.terminal.writeSuccess(`Claude Flow started in ${params.mode} mode`); break; case 'jay-code/stopped': this.terminal.writeInfo('Claude Flow stopped'); break; case 'jay-code/error': this.terminal.writeError(`Claude Flow error: ${params.message}`); break; default: this.terminal.writeInfo(`Claude Flow: ${method} - ${JSON.stringify(params)}`); } } /** * Handle agent status updates */ handleAgentStatus(params) { if (params.active !== undefined) { this.activeAgents = params.active; } if (params.message) { this.terminal.writeInfo(`Agent: ${params.message}`); } } /** * Handle swarm updates */ handleSwarmUpdate(params) { if (params.message) { this.terminal.writeInfo(`Swarm: ${params.message}`); } } /** * Handle memory updates */ handleMemoryUpdate(params) { if (params.message) { this.terminal.writeInfo(`Memory: ${params.message}`); } } /** * Handle log messages */ handleLogMessage(params) { if (params.level && params.message) { const type = params.level === 'error' ? 'error' : params.level === 'warn' ? 'warning' : 'info'; this.terminal.write(`[${params.level.toUpperCase()}] ${params.message}`, type); } } /** * Handle connection established notification */ handleConnectionEstablished(params) { // Log connection details without cluttering the terminal console.log('Connection established:', params); // Optionally show a brief success message // this.terminal.writeSuccess(`Connected to ${params.server} v${params.version}`); } /** * Handle interrupt (Ctrl+C) */ handleInterrupt() { // Could be used to cancel running commands this.terminal.writeWarning('Interrupt signal sent'); } /** * Handle setting changes */ handleSettingChange(key, value) { switch (key) { case 'theme': document.documentElement.setAttribute('data-theme', value); break; case 'fontSize': document.documentElement.style.setProperty('--font-size-base', `${value}px`); break; case 'lineHeight': document.documentElement.style.setProperty('--line-height', value); break; } } /** * Start status updates */ startStatusUpdates() { // Update status every 5 seconds this.statusInterval = setInterval(() => { this.updateStatus(); }, 5000); // Update timestamp every second this.uptimeInterval = setInterval(() => { this.updateUptime(); this.updateTimestamp(); }, 1000); // Initial update this.updateStatus(); this.updateUptime(); this.updateTimestamp(); } /** * Update status bar */ updateStatus() { // Update active agents if (this.elements.activeAgents) { this.elements.activeAgents.textContent = `Agents: ${this.activeAgents}`; } // Update message count if (this.elements.messageCount) { this.elements.messageCount.textContent = `Messages: ${this.messageCount}`; } // Update memory usage (if available) if (this.elements.memoryUsage && performance.memory) { const used = Math.round(performance.memory.usedJSHeapSize / 1024 / 1024); this.elements.memoryUsage.textContent = `Memory: ${used}MB`; } } /** * Update uptime */ updateUptime() { if (this.elements.uptime) { const uptime = Date.now() - this.startTime; const hours = Math.floor(uptime / (1000 * 60 * 60)); const minutes = Math.floor((uptime % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((uptime % (1000 * 60)) / 1000); this.elements.uptime.textContent = `Uptime: ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; } } /** * Update timestamp */ updateTimestamp() { if (this.elements.timestamp) { this.elements.timestamp.textContent = new Date().toLocaleTimeString(); } } /** * Toggle fullscreen mode */ toggleFullscreen() { if (!document.fullscreenElement) { document.documentElement.requestFullscreen().catch((err) => { console.error('Error attempting to enable fullscreen:', err); }); } else { document.exitFullscreen(); } } /** * Show loading overlay */ showLoading(message = 'Loading...') { if (this.elements.loadingOverlay) { const loadingText = this.elements.loadingOverlay.querySelector('.loading-text'); if (loadingText) { loadingText.textContent = message; } this.elements.loadingOverlay.classList.remove('hidden'); } } /** * Hide loading overlay */ hideLoading() { if (this.elements.loadingOverlay) { this.elements.loadingOverlay.classList.add('hidden'); } } /** * Show error message */ showError(message) { this.hideLoading(); if (this.terminal) { this.terminal.writeError(message); } else { // Fallback if terminal isn't initialized console.error(message); alert(message); } } /** * Setup global event listeners */ setupEventListeners() { // Handle unhandled promise rejections window.addEventListener('unhandledrejection', (event) => { console.error('Unhandled promise rejection:', event.reason); if (this.terminal) { this.terminal.writeError(`Unhandled error: ${event.reason.message || event.reason}`); } }); // Handle errors window.addEventListener('error', (event) => { console.error('Global error:', event.error); if (this.terminal) { this.terminal.writeError(`Application error: ${event.error.message || event.error}`); } }); } /** * Cleanup on shutdown */ cleanup() { // Clear intervals if (this.statusInterval) { clearInterval(this.statusInterval); } if (this.uptimeInterval) { clearInterval(this.uptimeInterval); } // Disconnect WebSocket if (this.wsClient) { this.wsClient.disconnect(); } } /** * Get console statistics */ getStats() { return { initialized: this.isInitialized, uptime: Date.now() - this.startTime, messageCount: this.messageCount, activeAgents: this.activeAgents, connection: this.wsClient.getStatus(), terminal: this.terminal ? this.terminal.getStats() : null, }; } } // Initialize the console when DOM is ready document.addEventListener('DOMContentLoaded', async () => { const console = new ClaudeCodeConsole(); // Make console globally available for debugging window.claudeConsole = console; // Initialize the application await console.init(); }); // Export for module usage export { ClaudeCodeConsole };