UNPKG

dragon-ui-claude

Version:

🐲 Ultra-fast, cross-platform Claude Code Max usage dashboard with dragon-inspired design, advanced background services, and multi-currency support

495 lines (428 loc) 14.5 kB
/** * CLI Dashboard - Main Menu with Live Updates (CommonJS) * Interactive command selection and dashboard data display */ const { createInterface } = require('readline'); const figlet = require('figlet'); const CLIDataAdapter = require('./data-adapter.cjs'); const { loadSettings, saveSettings, toggleSetting } = require('./settings.cjs'); const { colors, setColorMode, initChalk } = require('./components/colors.cjs'); const { createStatsTable } = require('./components/table.cjs'); const { showNavigationMenu, showPageHeader, parseCommand, getPageName, clearScreen, showLoading, showError, showSuccess, showHelp, waitForInput } = require('./utils/navigation.cjs'); const { createBox, createTimestampHeader } = require('./utils/screen.cjs'); // Import page modules const showOverviewPage = require('./pages/overview.cjs'); const showProjectsPage = require('./pages/projects.cjs'); const showSessionsPage = require('./pages/sessions.cjs'); const showMonthlyPage = require('./pages/monthly.cjs'); const showDailyPage = require('./pages/daily.cjs'); const showActivePage = require('./pages/active.cjs'); class CLIDashboard { constructor(settings) { this.settings = settings; this.dataAdapter = new CLIDataAdapter(settings); this.currentPage = 0; // 0 = main menu this.isRunning = false; this.refreshInterval = null; this.autoRefreshInterval = null; // Auto-refresh timer like Electron UI this.rl = null; this.lastUpdateTime = 0; // Apply color settings setColorMode(settings.useColors); } /** * Start the CLI dashboard */ async start() { // Clear screen immediately clearScreen(); this.isRunning = true; // Initialize chalk first await initChalk(); // Initialize data adapter const loadingStop = showLoading('Initializing database...'); const initialized = await this.dataAdapter.init(); loadingStop(); if (!initialized) { showError('Failed to initialize database. Please check your setup.'); process.exit(1); } // Auto-refresh data on startup to get latest JSONL entries console.log(''); console.log(colors.info('🔄 Refreshing data...')); console.log(colors.subtitle('• Scanning Claude project paths')); console.log(colors.subtitle('• Processing new JSONL files')); console.log(colors.subtitle('• Updating database with latest activity')); console.log(''); const startupRefresh = showLoading('Synchronizing with latest Claude activity...'); await this.dataAdapter.forceRefreshData(); startupRefresh(); console.log(colors.success('✓ Data refreshed successfully')); console.log(''); // Clear screen before showing welcome clearScreen(); // Show welcome screen await this.showWelcomeScreen(); // Start auto-refresh like Electron UI this.startAutoRefresh(); // Start main loop await this.mainLoop(); } /** * Show welcome screen with ASCII art */ async showWelcomeScreen() { clearScreen(); try { const asciiArt = figlet.textSync('DRAGON UI', { font: 'Small', horizontalLayout: 'fitted' }); console.log(colors.primary(asciiArt)); console.log(colors.subtitle('🐲 Claude Code Max Usage Dashboard - CLI Version')); console.log(colors.subtitle('─'.repeat(60))); console.log(''); console.log(colors.info('✓ Database connected')); console.log(colors.info('✓ Settings loaded')); console.log(colors.info('✓ Live updates enabled')); console.log(''); } catch (error) { console.log(colors.primary('🐲 DRAGON UI CLI')); console.log(colors.subtitle('Claude Code Max Usage Dashboard')); console.log(''); } } /** * Main application loop */ async mainLoop() { while (this.isRunning) { try { await this.displayCurrentPage(); await this.handleUserInput(); } catch (error) { showError('An error occurred: ' + error.message); await waitForInput(); } } } /** * Display current page content */ async displayCurrentPage() { clearScreen(); const timestamp = createTimestampHeader( `🐲 Dragon UI CLI - ${getPageName(this.currentPage)}`, 80 ); console.log(timestamp); console.log(''); switch (this.currentPage) { case 0: await this.showMainMenu(); break; case 1: await showOverviewPage(this.dataAdapter, this.settings); break; case 2: await showProjectsPage(this.dataAdapter, this.settings); break; case 3: await showSessionsPage(this.dataAdapter, this.settings); break; case 4: await showMonthlyPage(this.dataAdapter, this.settings); break; case 5: await showDailyPage(this.dataAdapter, this.settings); break; case 6: await showActivePage(this.dataAdapter, this.settings); break; default: await this.showMainMenu(); } } /** * Show main menu with dashboard summary */ async showMainMenu() { console.log(showNavigationMenu()); // Show dashboard summary try { const loadingStop = showLoading('Loading dashboard data...'); const data = await this.dataAdapter.getOverviewData(); loadingStop(); console.log(colors.primary('Dashboard Summary:')); console.log('─'.repeat(50)); console.log(''); // Quick stats const table = createStatsTable({ totalCost: data.totalCost, totalSessions: data.totalSessions, totalProjects: data.totalProjects, totalTokens: data.totalTokens, activeDays: data.activeDays, lastActivity: data.lastActivity, currency: data.currency }, { style: this.settings.tableStyle, compact: this.settings.compactMode }); console.log(table); console.log(''); // Current month highlight if (data.currentMonth && data.currentMonth.total_cost > 0) { console.log(colors.highlight('This Month: ') + colors.currency(`$${data.currentMonth.total_cost.toFixed(6)} ${data.currency}`) + ' (' + colors.number(data.currentMonth.session_count.toString()) + ' sessions)'); console.log(''); } // Settings info console.log(colors.subtitle(`Settings: ${this.settings.useColors ? 'Color' : 'Monochrome'} • ${this.settings.tableStyle} tables • ${this.settings.refreshInterval/1000}s refresh`)); console.log(''); } catch (error) { showError('Failed to load dashboard data: ' + error.message); console.log(''); } } /** * Handle user input */ async handleUserInput() { return new Promise((resolve) => { if (this.rl) { this.rl.close(); } this.rl = createInterface({ input: process.stdin, output: process.stdout }); this.rl.question(colors.primary('Enter command (0=Menu, 1-6=Pages, q=Quit, r=Refresh, s=Settings, h=Help): '), async (input) => { const command = parseCommand(input); switch (command.type) { case 'page': await this.handlePageCommand(command.value); break; case 'quit': await this.handleQuit(); break; case 'refresh': await this.handleRefresh(); break; case 'settings': await this.handleSettings(); break; case 'help': await this.handleHelp(); break; case 'clear': clearScreen(); break; case 'invalid': showError(`Invalid command: ${command.value}`); await waitForInput(); break; } resolve(); }); }); } /** * Handle page navigation */ async handlePageCommand(pageNumber) { if (pageNumber >= 0 && pageNumber <= 6) { this.currentPage = pageNumber; // If going to a specific page, refresh data if (pageNumber > 0) { this.dataAdapter.clearCache(); } } else { showError('Invalid page number. Use 0-6.'); await waitForInput(); } } /** * Handle quit command */ async handleQuit() { console.log(''); console.log(colors.primary('🐲 Thanks for using Dragon UI CLI!')); console.log(colors.subtitle('May your code be bug-free and your dragons happy.')); console.log(''); this.isRunning = false; // Cleanup auto-refresh and resources this.cleanup(); if (this.refreshInterval) { clearInterval(this.refreshInterval); } process.exit(0); } /** * Handle refresh command */ async handleRefresh() { const loadingStop = showLoading('Refreshing data...'); try { await this.dataAdapter.refreshAll(); loadingStop(); showSuccess('Data refreshed successfully!'); } catch (error) { loadingStop(); showError('Failed to refresh data: ' + error.message); } await waitForInput(); } /** * Handle settings menu */ async handleSettings() { clearScreen(); console.log(colors.header('🐲 Dragon UI CLI - Settings')); console.log('─'.repeat(50)); console.log(''); console.log('Current Settings:'); console.log(` Colors: ${this.settings.useColors ? colors.success('Enabled') : colors.inactive('Disabled')}`); console.log(` Table Style: ${colors.highlight(this.settings.tableStyle)}`); console.log(` Refresh Interval: ${colors.number(this.settings.refreshInterval/1000)}s`); console.log(` Compact Mode: ${this.settings.compactMode ? colors.success('Enabled') : colors.inactive('Disabled')}`); console.log(''); console.log('Commands:'); console.log(' 1 - Toggle colors'); console.log(' 2 - Toggle table style'); console.log(' 3 - Toggle compact mode'); console.log(' 4 - Set refresh interval'); console.log(' 0 - Back to main menu'); console.log(''); const settingsRl = createInterface({ input: process.stdin, output: process.stdout }); return new Promise((resolve) => { settingsRl.question(colors.primary('Enter setting command: '), async (input) => { const command = input.trim(); switch (command) { case '1': this.settings = await toggleSetting('colors'); setColorMode(this.settings.useColors); showSuccess(this.settings.useColors ? 'Colors enabled' : 'Colors disabled'); break; case '2': this.settings = await toggleSetting('tables'); showSuccess(`Table style: ${this.settings.tableStyle}`); break; case '3': this.settings = await toggleSetting('compact'); showSuccess(this.settings.compactMode ? 'Compact mode enabled' : 'Compact mode disabled'); break; case '4': settingsRl.question('Enter refresh interval (1-60 seconds): ', async (seconds) => { const interval = parseInt(seconds); if (interval >= 1 && interval <= 60) { this.settings.refreshInterval = interval * 1000; await saveSettings(this.settings); showSuccess(`Refresh interval set to ${interval} seconds`); } else { showError('Invalid interval. Use 1-60 seconds.'); } settingsRl.close(); await waitForInput(); resolve(); }); return; case '0': break; default: showError('Invalid command'); } settingsRl.close(); await waitForInput(); resolve(); }); }); } /** * Handle help command */ async handleHelp() { clearScreen(); console.log(showHelp()); await waitForInput(); } /** * Start auto-refresh like Electron UI */ startAutoRefresh() { // Clear any existing interval if (this.autoRefreshInterval) { clearInterval(this.autoRefreshInterval); } // Start auto-refresh using user's configured interval this.autoRefreshInterval = setInterval(async () => { if (this.isRunning) { try { // Force refresh database to get latest data await this.dataAdapter.forceRefreshData(); // Refresh current page display (including main menu) await this.refreshCurrentPageSilent(); } catch (error) { // Silent error - don't interrupt user experience } } }, this.settings.refreshInterval); // Use user's configured interval } /** * Stop auto-refresh */ stopAutoRefresh() { if (this.autoRefreshInterval) { clearInterval(this.autoRefreshInterval); this.autoRefreshInterval = null; } } /** * Silently refresh current page without clearing screen */ async refreshCurrentPageSilent() { try { // Show subtle refresh indicator in corner const timestamp = new Date().toLocaleTimeString(); process.stdout.write(`\r${colors.subtitle(`⟳ ${timestamp}`)}`); // Wait briefly then clear the indicator setTimeout(() => { process.stdout.write('\r \r'); }, 1000); } catch (error) { // Silent error } } /** * Cleanup on exit */ cleanup() { this.stopAutoRefresh(); if (this.rl) { this.rl.close(); } } } /** * Start CLI dashboard */ async function startCLI(settings) { const dashboard = new CLIDashboard(settings); await dashboard.start(); } module.exports = { startCLI, CLIDashboard };