Add interactive plugin installation
This commit is contained in:
@@ -4,6 +4,11 @@ import { join, resolve, dirname } from 'path';
|
||||
import { existsSync } from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import fetch from 'node-fetch';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import readline from 'readline';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
@@ -291,6 +296,99 @@ export class PluginHost extends EventEmitter {
|
||||
return Array.from(this.pluginRegistry.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a plugin from its repository
|
||||
*/
|
||||
async installPlugin(pluginId) {
|
||||
const pluginDef = this.pluginRegistry.get(pluginId);
|
||||
if (!pluginDef) {
|
||||
throw new Error(`Plugin ${pluginId} not found in registry`);
|
||||
}
|
||||
|
||||
const pluginPath = join(this.pluginsDir, pluginId);
|
||||
if (existsSync(pluginPath)) {
|
||||
console.log(`⚠ Plugin ${pluginId} already installed`);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log(`📥 Installing ${pluginDef.name} v${pluginDef.version}...`);
|
||||
|
||||
try {
|
||||
await execAsync(`git clone ${pluginDef.repository} "${pluginPath}"`, {
|
||||
cwd: this.pluginsDir
|
||||
});
|
||||
|
||||
console.log(`✅ Installed ${pluginDef.name}`);
|
||||
this.emit('plugin-installed', pluginDef);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to install ${pluginId}:`, error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interactive plugin installation
|
||||
*/
|
||||
async interactiveInstall() {
|
||||
const available = this.getRegistry();
|
||||
const loadedIds = new Set(this.getLoadedPlugins().map(p => p.id));
|
||||
const installedPluginDirs = existsSync(this.pluginsDir)
|
||||
? await readdir(this.pluginsDir, { withFileTypes: true }).then(entries =>
|
||||
entries.filter(e => e.isDirectory()).map(e => e.name)
|
||||
)
|
||||
: [];
|
||||
const installedIds = new Set(installedPluginDirs);
|
||||
|
||||
const installable = available.filter(p => !installedIds.has(p.id));
|
||||
|
||||
if (installable.length === 0) {
|
||||
console.log('\n✨ All available plugins are already installed!');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('\n📦 Available Plugins to Install:');
|
||||
console.log('================================');
|
||||
installable.forEach((p, i) => {
|
||||
console.log(`${i + 1}. ${p.name} v${p.version} by ${p.author}`);
|
||||
console.log(` ${p.description || 'No description'}`);
|
||||
});
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
const question = (query) => new Promise(resolve => rl.question(query, resolve));
|
||||
|
||||
console.log('\nEnter plugin numbers to install (comma-separated), or "all" for all plugins:');
|
||||
const answer = await question('> ');
|
||||
|
||||
rl.close();
|
||||
|
||||
let toInstall = [];
|
||||
|
||||
if (answer.toLowerCase().trim() === 'all') {
|
||||
toInstall = installable;
|
||||
} else {
|
||||
const indices = answer.split(',').map(s => parseInt(s.trim()) - 1).filter(i => !isNaN(i));
|
||||
toInstall = indices.map(i => installable[i]).filter(Boolean);
|
||||
}
|
||||
|
||||
if (toInstall.length === 0) {
|
||||
console.log('\n❌ No valid plugins selected');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`\n📥 Installing ${toInstall.length} plugin(s)...`);
|
||||
|
||||
for (const plugin of toInstall) {
|
||||
await this.installPlugin(plugin.id);
|
||||
}
|
||||
|
||||
console.log('\n✅ Installation complete!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the plugin host
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user