import { ComponentInstaller, InstallationError } from './types'; import { BackupManager } from './backup-manager'; import { StatuslineInstaller } from './statusline-installer'; export class TokenNerdInstaller { private installers: ComponentInstaller[]; private backupManager: BackupManager; constructor() { // Statusline installation is now manual - see README for setup instructions this.installers = [ // new StatuslineInstaller() ]; this.backupManager = new BackupManager(); } async install(): Promise { console.log('๐Ÿ”ง Setting up Token Nerd...\n'); const installedComponents: string[] = []; try { // Clean up old backups first await this.backupManager.cleanupOldBackups(); if (this.installers.length === 0) { console.log('โ„น๏ธ No automatic installers configured - using manual setup mode\n'); } for (const installer of this.installers) { await installer.install(); installedComponents.push(installer.getName()); console.log(); } console.log('โœ… Token Nerd installation complete!\n'); console.log('๐Ÿ“– NEXT STEP: Set up your statusline for real-time token tracking:'); console.log(' See README.md for complete setup instructions'); console.log(' Quick setup: https://github.com/StupidIncarnate/token-nerd#statusline-setup'); console.log('\nAfter setup, use "token-nerd" anytime to analyze your token usage!'); } catch (error) { console.error('โŒ Installation failed:', error instanceof Error ? error.message : error); // Attempt to rollback installed components console.log('\n๐Ÿ”„ Attempting to rollback...'); try { await this.rollback(installedComponents); console.log('โœ“ Rollback completed'); } catch (rollbackError) { console.error('โŒ Rollback failed:', rollbackError instanceof Error ? rollbackError.message : rollbackError); console.error('Manual cleanup may be required'); } throw error; } } async uninstall(): Promise { console.log('๐Ÿงน Cleaning up Token Nerd installation...\n'); const errors: Error[] = []; // Uninstall in reverse order for (const installer of [...this.installers].reverse()) { try { await installer.uninstall(); console.log(); } catch (error) { errors.push(error instanceof Error ? error : new Error(String(error))); console.error(`โš ๏ธ Failed to uninstall ${installer.getName()}: ${error}`); } } // Clean up data and backups console.log('๐Ÿ—‘๏ธ Cleaning up token-nerd data...'); try { // Clean up installation state and old backups await this.backupManager.cleanupOldBackups(0); // Remove all backups const state = await this.backupManager.getInstallationState(); state.installedComponents = []; state.backups = []; await this.backupManager.saveInstallationState(state); console.log('โœ“ Data cleanup complete'); } catch (error) { errors.push(error instanceof Error ? error : new Error(String(error))); console.error(`โš ๏ธ Data cleanup failed: ${error}`); } if (errors.length === 0) { console.log('โœ… Token Nerd cleanup complete!'); console.log('๐Ÿ”„ Restart Claude to finish cleanup'); } else { console.log(`โš ๏ธ Cleanup completed with ${errors.length} error(s)`); console.log('Some manual cleanup may be needed'); throw new InstallationError( `Uninstall completed with errors: ${errors.map(e => e.message).join('; ')}`, 'all', 'uninstall' ); } } async getStatus(): Promise<{ [key: string]: boolean }> { const status: { [key: string]: boolean } = {}; for (const installer of this.installers) { try { status[installer.getName()] = await installer.isInstalled(); } catch (error) { status[installer.getName()] = false; } } return status; } async validate(): Promise<{ [key: string]: boolean }> { const validation: { [key: string]: boolean } = {}; for (const installer of this.installers) { try { validation[installer.getName()] = await installer.validate(); } catch (error) { validation[installer.getName()] = false; } } return validation; } async isFullyInstalled(): Promise { const status = await this.getStatus(); return Object.values(status).every(installed => installed); } async isFullyValid(): Promise { const validation = await this.validate(); return Object.values(validation).every(valid => valid); } private async rollback(installedComponents: string[]): Promise { // Rollback in reverse order of installation for (const componentName of [...installedComponents].reverse()) { const installer = this.installers.find(i => i.getName() === componentName); if (installer) { try { await installer.uninstall(); console.log(`โœ“ Rolled back ${componentName}`); } catch (rollbackError) { console.error(`โŒ Failed to rollback ${componentName}: ${rollbackError}`); // Continue with other rollbacks } } } } async repairInstallation(): Promise { console.log('๐Ÿ”ง Repairing Token Nerd installation...\n'); const status = await this.getStatus(); const validation = await this.validate(); let hasIssues = false; for (const installer of this.installers) { const name = installer.getName(); const installed = status[name]; const valid = validation[name]; if (!installed) { console.log(`โš ๏ธ ${name} is not installed - reinstalling...`); await installer.install(); hasIssues = true; } else if (!valid) { console.log(`โš ๏ธ ${name} installation is invalid - repairing...`); await installer.uninstall(); await installer.install(); hasIssues = true; } else { console.log(`โœ“ ${name} is properly installed`); } } if (!hasIssues) { console.log('\nโœ… Token Nerd installation is healthy - no repairs needed'); } else { console.log('\nโœ… Token Nerd installation repaired successfully'); console.log('๐Ÿ”„ Restart Claude to ensure all changes take effect'); } } }