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
JavaScript
/**
* 🔄 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