UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

509 lines 17.3 kB
/** * 🔄 AI-Debug Auto-Update System v4.1.0 * * Revolutionary auto-update mechanism for AI-Debug MCP Server * Ensures users always have the latest universal recording capabilities * * Features: * - Automatic version checking * - Background updates without interruption * - Rollback capability for safety * - Configurable update channels (stable, beta, nightly) * - Update notifications through MCP protocol */ import { exec } from 'child_process'; import { promisify } from 'util'; import * as fs from 'fs/promises'; import * as fsSync from 'fs'; import * as path from 'path'; import fetch from 'node-fetch'; import { EventEmitter } from 'events'; import { createHash } from 'crypto'; import * as semver from 'semver'; const execAsync = promisify(exec); export class AutoUpdater extends EventEmitter { config; status; checkTimer; updateLock = false; packageJsonPath; currentVersion; constructor(config) { super(); this.config = { enabled: true, checkInterval: 24 * 60 * 60 * 1000, // 24 hours channel: 'stable', autoDownload: true, autoInstall: false, notifyOnly: false, githubRepo: 'ai-debug/ai-debug-local-mcp', npmRegistry: 'https://registry.npmjs.org', preserveLocalChanges: true, backupBeforeUpdate: true, ...config }; this.packageJsonPath = path.join(process.cwd(), 'package.json'); this.currentVersion = this.getCurrentVersion(); this.status = { checking: false, downloading: false, installing: false, available: false, current: this.currentVersion, latest: this.currentVersion, lastCheck: new Date() }; if (this.config.enabled) { this.startAutoCheck(); } } /** * Get current installed version */ getCurrentVersion() { try { // Use synchronous fs to read package.json during initialization const packageJsonContent = fsSync.readFileSync(this.packageJsonPath, 'utf-8'); const packageJson = JSON.parse(packageJsonContent); return packageJson.version; } catch (error) { // For ES module environment, default to current version return '4.1.0'; } } /** * Start automatic update checking */ startAutoCheck() { // Initial check after 30 seconds setTimeout(() => this.checkForUpdates(), 30000); // Regular interval checks this.checkTimer = setInterval(() => this.checkForUpdates(), this.config.checkInterval); } /** * Check for available updates */ async checkForUpdates() { if (this.updateLock || this.status.checking) { return null; } this.status.checking = true; this.emit('checking-for-update'); try { // Check GitHub releases for latest version const latestInfo = await this.fetchLatestRelease(); if (!latestInfo) { this.status.checking = false; return null; } const hasUpdate = semver.gt(latestInfo.version, this.currentVersion); this.status.latest = latestInfo.version; this.status.available = hasUpdate; this.status.lastCheck = new Date(); this.status.checking = false; if (hasUpdate) { this.emit('update-available', latestInfo); if (this.config.autoDownload && !this.config.notifyOnly) { await this.downloadUpdate(latestInfo); } return latestInfo; } else { this.emit('update-not-available'); return null; } } catch (error) { this.status.checking = false; this.status.error = error instanceof Error ? error.message : String(error); this.emit('error', error); return null; } } /** * Fetch latest release information from GitHub */ async fetchLatestRelease() { try { const url = `https://api.github.com/repos/${this.config.githubRepo}/releases/latest`; const response = await fetch(url, { headers: { 'User-Agent': 'AI-Debug-Auto-Updater', 'Accept': 'application/vnd.github.v3+json' } }); if (!response.ok) { // Fallback to npm registry return await this.fetchFromNpm(); } const release = await response.json(); // Find the npm package asset const packageAsset = release.assets?.find((asset) => asset.name.endsWith('.tgz')); return { version: release.tag_name.replace('v', ''), releaseNotes: release.body || '', publishedAt: new Date(release.published_at), downloadUrl: packageAsset?.browser_download_url || '', size: packageAsset?.size || 0, sha256: '', // Would need to fetch separately breaking: release.name?.includes('BREAKING') || false, features: this.extractFeatures(release.body), fixes: this.extractFixes(release.body), securityPatches: release.body?.toLowerCase().includes('security') || false }; } catch (error) { console.error('Failed to fetch GitHub release:', error); return await this.fetchFromNpm(); } } /** * Fallback to fetch from npm registry */ async fetchFromNpm() { try { const url = `${this.config.npmRegistry}/ai-debug-local-mcp`; const response = await fetch(url); if (!response.ok) { return null; } const data = await response.json(); const latest = data['dist-tags'][this.config.channel] || data['dist-tags'].latest; const versionData = data.versions[latest]; return { version: latest, releaseNotes: versionData.description || '', publishedAt: new Date(data.time[latest]), downloadUrl: versionData.dist.tarball, size: versionData.dist.unpackedSize || 0, sha256: versionData.dist.integrity || '', breaking: false, features: [], fixes: [], securityPatches: false }; } catch (error) { console.error('Failed to fetch from npm:', error); return null; } } /** * Download update package */ async downloadUpdate(updateInfo) { if (this.status.downloading) { throw new Error('Update already downloading'); } this.status.downloading = true; this.emit('download-progress', { percent: 0 }); try { const response = await fetch(updateInfo.downloadUrl); const buffer = await response.buffer(); // Verify integrity if SHA256 is provided if (updateInfo.sha256) { const hash = createHash('sha256').update(buffer).digest('hex'); if (hash !== updateInfo.sha256) { throw new Error('Package integrity check failed'); } } // Save to temp directory const tempPath = path.join(process.env.TMPDIR || '/tmp', `ai-debug-update-${updateInfo.version}.tgz`); await fs.writeFile(tempPath, buffer); this.status.downloading = false; this.emit('update-downloaded', updateInfo); if (this.config.autoInstall && !this.config.notifyOnly) { await this.installUpdate(tempPath, updateInfo); } return tempPath; } catch (error) { this.status.downloading = false; this.status.error = error instanceof Error ? error.message : String(error); this.emit('error', error); throw error; } } /** * Install downloaded update */ async installUpdate(packagePath, updateInfo) { if (this.status.installing || this.updateLock) { throw new Error('Update already in progress'); } this.updateLock = true; this.status.installing = true; this.emit('before-install', updateInfo); try { // Backup current installation if configured if (this.config.backupBeforeUpdate) { await this.backupCurrentInstallation(); } // Check if we're running as global npm package const isGlobal = await this.isGlobalInstall(); if (isGlobal) { // Update global package await this.updateGlobalPackage(packagePath); } else { // Update local package await this.updateLocalPackage(packagePath); } // Update current version this.currentVersion = updateInfo.version; this.status.current = updateInfo.version; this.status.available = false; this.status.installing = false; this.updateLock = false; this.emit('update-installed', updateInfo); // Restart required for changes to take effect this.emit('restart-required'); } catch (error) { this.status.installing = false; this.updateLock = false; this.status.error = error instanceof Error ? error.message : String(error); this.emit('error', error); // Attempt rollback if installation failed if (this.config.backupBeforeUpdate) { await this.rollbackUpdate(); } throw error; } } /** * Check if running as global npm install */ async isGlobalInstall() { try { const { stdout } = await execAsync('npm list -g ai-debug-local-mcp --depth=0'); return stdout.includes('ai-debug-local-mcp'); } catch { return false; } } /** * Update global npm package */ async updateGlobalPackage(packagePath) { await execAsync(`npm install -g ${packagePath}`); } /** * Update local npm package */ async updateLocalPackage(packagePath) { await execAsync(`npm install ${packagePath}`); } /** * Backup current installation */ async backupCurrentInstallation() { const backupDir = path.join(process.env.HOME || process.env.USERPROFILE || '/tmp', '.ai-debug-backups', `backup-${this.currentVersion}-${Date.now()}`); await fs.mkdir(backupDir, { recursive: true }); // Copy important files const filesToBackup = [ 'package.json', 'dist', 'bin', '.env', 'claude.json' ]; for (const file of filesToBackup) { const sourcePath = path.join(process.cwd(), file); const targetPath = path.join(backupDir, file); try { const stats = await fs.stat(sourcePath); if (stats.isDirectory()) { await this.copyDirectory(sourcePath, targetPath); } else { await fs.copyFile(sourcePath, targetPath); } } catch (error) { // File might not exist, continue } } this.emit('backup-created', backupDir); } /** * Rollback to previous version */ async rollbackUpdate() { // Implementation would restore from backup this.emit('rollback-initiated'); } /** * Copy directory recursively */ async copyDirectory(source, target) { await fs.mkdir(target, { recursive: true }); const entries = await fs.readdir(source, { withFileTypes: true }); for (const entry of entries) { const sourcePath = path.join(source, entry.name); const targetPath = path.join(target, entry.name); if (entry.isDirectory()) { await this.copyDirectory(sourcePath, targetPath); } else { await fs.copyFile(sourcePath, targetPath); } } } /** * Extract features from release notes */ extractFeatures(body) { const features = []; const lines = body?.split('\n') || []; for (const line of lines) { if (line.includes('✨') || line.includes('🚀') || line.includes('feat:')) { features.push(line.trim()); } } return features; } /** * Extract fixes from release notes */ extractFixes(body) { const fixes = []; const lines = body?.split('\n') || []; for (const line of lines) { if (line.includes('🐛') || line.includes('fix:') || line.includes('Fixed')) { fixes.push(line.trim()); } } return fixes; } /** * Get current update status */ getStatus() { return { ...this.status }; } /** * Configure auto-updater */ configure(config) { this.config = { ...this.config, ...config }; if (this.config.enabled && !this.checkTimer) { this.startAutoCheck(); } else if (!this.config.enabled && this.checkTimer) { clearInterval(this.checkTimer); this.checkTimer = undefined; } } /** * Manually trigger update check */ async checkNow() { return await this.checkForUpdates(); } /** * Install specific version */ async installVersion(version) { const updateInfo = { version, releaseNotes: `Manual update to ${version}`, publishedAt: new Date(), downloadUrl: `${this.config.npmRegistry}/ai-debug-local-mcp/-/ai-debug-local-mcp-${version}.tgz`, size: 0, sha256: '', breaking: false, features: [], fixes: [], securityPatches: false }; const packagePath = await this.downloadUpdate(updateInfo); await this.installUpdate(packagePath, updateInfo); } /** * Clean up resources */ destroy() { if (this.checkTimer) { clearInterval(this.checkTimer); this.checkTimer = undefined; } this.removeAllListeners(); } } /** * Create MCP tool for update management */ export function createUpdateTools() { return [ { name: 'check_for_updates', description: '🔄 Check for available AI-Debug updates', inputSchema: { type: 'object', properties: { channel: { type: 'string', enum: ['stable', 'beta', 'nightly'], description: 'Update channel to check' } } } }, { name: 'install_update', description: '⬆️ Install available AI-Debug update', inputSchema: { type: 'object', properties: { version: { type: 'string', description: 'Specific version to install (optional)' }, backup: { type: 'boolean', description: 'Create backup before updating', default: true } } } }, { name: 'configure_auto_update', description: '⚙️ Configure auto-update settings', inputSchema: { type: 'object', properties: { enabled: { type: 'boolean', description: 'Enable auto-updates' }, channel: { type: 'string', enum: ['stable', 'beta', 'nightly'] }, autoInstall: { type: 'boolean', description: 'Automatically install updates' }, checkInterval: { type: 'number', description: 'Check interval in hours' } } } }, { name: 'get_update_status', description: '📊 Get current update status', inputSchema: { type: 'object', properties: {} } } ]; } //# sourceMappingURL=auto-updater.js.map