UNPKG

@ace-sdk/cli

Version:

ACE CLI - Command-line tool for intelligent pattern learning and playbook management

282 lines 8.91 kB
/** * Plugin loader and management service */ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'fs'; import { homedir } from 'os'; import { join } from 'path'; /** * Plugin loader - discovers, loads, and manages ACE CLI plugins * * Plugins are stored in ~/.ace/plugins/ * Plugin registry is stored in ~/.ace/plugins.json */ export class PluginLoader { pluginsDir; registryFile; registry; loadedPlugins; logger; constructor(logger) { this.logger = logger; this.pluginsDir = join(homedir(), '.ace', 'plugins'); this.registryFile = join(homedir(), '.ace', 'plugins.json'); this.loadedPlugins = new Map(); // Ensure plugins directory exists if (!existsSync(this.pluginsDir)) { mkdirSync(this.pluginsDir, { recursive: true }); } // Load registry this.registry = this.loadRegistry(); } /** * Load plugin registry from disk */ loadRegistry() { if (!existsSync(this.registryFile)) { return { trusted: [], enabled: [], disabled: [] }; } try { const data = readFileSync(this.registryFile, 'utf8'); return JSON.parse(data); } catch { return { trusted: [], enabled: [], disabled: [] }; } } /** * Save plugin registry to disk */ saveRegistry() { writeFileSync(this.registryFile, JSON.stringify(this.registry, null, 2)); } /** * Get plugin registry */ getRegistry() { return { ...this.registry }; } /** * Discover all plugins in plugins directory */ discoverPlugins() { if (!existsSync(this.pluginsDir)) { return []; } const plugins = []; const entries = readdirSync(this.pluginsDir); for (const entry of entries) { const pluginPath = join(this.pluginsDir, entry); const stat = statSync(pluginPath); if (stat.isDirectory()) { try { const manifest = this.loadManifest(pluginPath); plugins.push(manifest); } catch (error) { this.logger.debug(`Failed to load plugin manifest: ${entry}`, error); } } } return plugins; } /** * Load plugin manifest from directory */ loadManifest(pluginPath) { const manifestFile = join(pluginPath, 'plugin.json'); if (!existsSync(manifestFile)) { throw new Error('Plugin manifest (plugin.json) not found'); } const data = readFileSync(manifestFile, 'utf8'); const manifest = JSON.parse(data); // Validate required fields if (!manifest.name || !manifest.version || !manifest.main) { throw new Error('Invalid plugin manifest: missing required fields (name, version, main)'); } return manifest; } /** * Load and activate a plugin */ async loadPlugin(name, context) { // Check if already loaded if (this.loadedPlugins.has(name)) { return this.loadedPlugins.get(name); } // Find plugin directory const pluginPath = join(this.pluginsDir, name); if (!existsSync(pluginPath)) { throw new Error(`Plugin not found: ${name}`); } // Load manifest const manifest = this.loadManifest(pluginPath); // Check if plugin is trusted const trusted = this.isTrusted(manifest); if (!trusted) { throw new Error(`Plugin "${name}" is not trusted. Run "ace-cli plugin trust ${name}" to trust this plugin.`); } // Check if plugin is enabled const enabled = this.isEnabled(manifest.name); // Load plugin module const mainFile = join(pluginPath, manifest.main); if (!existsSync(mainFile)) { throw new Error(`Plugin entry point not found: ${manifest.main}`); } let module; try { module = await import(mainFile); } catch (error) { throw new Error(`Failed to load plugin module: ${error instanceof Error ? error.message : String(error)}`); } // Create plugin instance const plugin = { metadata: manifest, path: pluginPath, module, enabled, trusted }; // Initialize plugin if it has an init function if (module.init && typeof module.init === 'function') { try { await module.init(context); } catch (error) { throw new Error(`Plugin initialization failed: ${error instanceof Error ? error.message : String(error)}`); } } // Store loaded plugin this.loadedPlugins.set(name, plugin); return plugin; } /** * Check if plugin is trusted */ isTrusted(manifest) { return this.registry.trusted.some(trust => trust.name === manifest.name && trust.version === manifest.version); } /** * Check if plugin is enabled */ isEnabled(name) { // Enabled by default if not explicitly disabled return !this.registry.disabled.includes(name); } /** * Trust a plugin */ trustPlugin(name, version) { // Remove existing trust for this plugin this.registry.trusted = this.registry.trusted.filter(t => t.name !== name); // Add new trust const trust = { name, version, trustedAt: new Date().toISOString(), trustedBy: 'user' }; this.registry.trusted.push(trust); this.saveRegistry(); } /** * Untrust a plugin */ untrustPlugin(name) { this.registry.trusted = this.registry.trusted.filter(t => t.name !== name); this.saveRegistry(); // Unload plugin if loaded this.loadedPlugins.delete(name); } /** * Enable a plugin */ enablePlugin(name) { // Remove from disabled list this.registry.disabled = this.registry.disabled.filter(p => p !== name); // Add to enabled list if not already there if (!this.registry.enabled.includes(name)) { this.registry.enabled.push(name); } this.saveRegistry(); } /** * Disable a plugin */ disablePlugin(name) { // Remove from enabled list this.registry.enabled = this.registry.enabled.filter(p => p !== name); // Add to disabled list if not already there if (!this.registry.disabled.includes(name)) { this.registry.disabled.push(name); } this.saveRegistry(); // Unload plugin if loaded this.loadedPlugins.delete(name); } /** * Get loaded plugin */ getPlugin(name) { return this.loadedPlugins.get(name); } /** * List all loaded plugins */ listLoadedPlugins() { return Array.from(this.loadedPlugins.values()); } /** * Execute a plugin command */ async executePluginCommand(pluginName, commandName, args, context) { // Load plugin if not loaded let plugin = this.loadedPlugins.get(pluginName); if (!plugin) { plugin = await this.loadPlugin(pluginName, context); } // Check if plugin is enabled if (!plugin.enabled) { throw new Error(`Plugin "${pluginName}" is disabled. Run "ace-cli plugin enable ${pluginName}" to enable it.`); } // Find command in manifest const command = plugin.metadata.commands?.find(cmd => cmd.name === commandName); if (!command) { throw new Error(`Command "${commandName}" not found in plugin "${pluginName}"`); } // Get handler function from module const handler = plugin.module[command.handler]; if (!handler || typeof handler !== 'function') { throw new Error(`Handler function "${command.handler}" not found in plugin module`); } // Execute handler try { return await handler(args, context); } catch (error) { throw new Error(`Plugin command failed: ${error instanceof Error ? error.message : String(error)}`); } } } /** * Singleton plugin loader instance */ let loaderInstance = null; /** * Get the global plugin loader instance */ export function getPluginLoader(logger) { if (!loaderInstance) { loaderInstance = new PluginLoader(logger); } return loaderInstance; } //# sourceMappingURL=plugin-loader.js.map