llm-checker
Version:
Intelligent CLI tool with AI-powered model selection that analyzes your hardware and recommends optimal LLM models for your system
285 lines (235 loc) • 8.45 kB
JavaScript
const fs = require('fs');
const path = require('path');
const { getLogger } = require('../utils/logger');
class PluginManager {
constructor(options = {}) {
this.pluginsDir = options.pluginsDir || path.join(process.cwd(), 'plugins');
this.plugins = new Map();
this.hooks = new Map();
this.logger = getLogger().createChild('PluginManager');
this.enabled = options.enabled !== false;
}
async loadPlugins() {
if (!this.enabled) {
this.logger.debug('Plugin system disabled');
return;
}
if (!fs.existsSync(this.pluginsDir)) {
this.logger.debug('Plugins directory not found, skipping plugin loading');
return;
}
try {
const pluginFiles = fs.readdirSync(this.pluginsDir)
.filter(file => file.endsWith('.js') || file.endsWith('.json'));
for (const file of pluginFiles) {
await this.loadPlugin(path.join(this.pluginsDir, file));
}
this.logger.info(`Loaded ${this.plugins.size} plugins`);
} catch (error) {
this.logger.error('Failed to load plugins', { error });
}
}
async loadPlugin(pluginPath) {
try {
const pluginName = path.basename(pluginPath, path.extname(pluginPath));
// Load plugin configuration or code
let plugin;
if (pluginPath.endsWith('.json')) {
plugin = JSON.parse(fs.readFileSync(pluginPath, 'utf8'));
plugin.type = 'config';
} else {
plugin = require(pluginPath);
plugin.type = 'code';
}
// Validate plugin
if (!this.validatePlugin(plugin)) {
this.logger.warn(`Invalid plugin: ${pluginName}`);
return false;
}
// Initialize plugin
if (plugin.initialize && typeof plugin.initialize === 'function') {
await plugin.initialize(this);
}
// Register plugin
this.plugins.set(pluginName, {
...plugin,
name: pluginName,
path: pluginPath,
loaded: true
});
// Register hooks
if (plugin.hooks) {
this.registerHooks(pluginName, plugin.hooks);
}
this.logger.debug(`Plugin loaded: ${pluginName}`, {
data: { type: plugin.type, version: plugin.version }
});
return true;
} catch (error) {
this.logger.error(`Failed to load plugin: ${pluginPath}`, { error });
return false;
}
}
validatePlugin(plugin) {
// Basic plugin validation
if (!plugin.name || !plugin.version) {
return false;
}
if (plugin.type === 'code' && !plugin.execute && !plugin.hooks) {
return false;
}
return true;
}
registerHooks(pluginName, hooks) {
for (const [hookName, hookFunction] of Object.entries(hooks)) {
if (!this.hooks.has(hookName)) {
this.hooks.set(hookName, []);
}
this.hooks.get(hookName).push({
plugin: pluginName,
function: hookFunction
});
}
}
async executeHook(hookName, data = {}) {
if (!this.hooks.has(hookName)) {
return data;
}
let result = data;
const hookFunctions = this.hooks.get(hookName);
for (const hook of hookFunctions) {
try {
this.logger.trace(`Executing hook: ${hookName} from plugin: ${hook.plugin}`);
result = await hook.function(result, this) || result;
} catch (error) {
this.logger.error(`Hook execution failed`, {
data: { hook: hookName, plugin: hook.plugin },
error
});
}
}
return result;
}
async executePlugin(pluginName, ...args) {
const plugin = this.plugins.get(pluginName);
if (!plugin) {
throw new Error(`Plugin not found: ${pluginName}`);
}
if (!plugin.execute || typeof plugin.execute !== 'function') {
throw new Error(`Plugin ${pluginName} is not executable`);
}
try {
this.logger.debug(`Executing plugin: ${pluginName}`);
return await plugin.execute(...args);
} catch (error) {
this.logger.error(`Plugin execution failed: ${pluginName}`, { error });
throw error;
}
}
getPlugin(name) {
return this.plugins.get(name);
}
listPlugins() {
return Array.from(this.plugins.values()).map(plugin => ({
name: plugin.name,
version: plugin.version,
description: plugin.description,
type: plugin.type,
loaded: plugin.loaded
}));
}
unloadPlugin(name) {
const plugin = this.plugins.get(name);
if (!plugin) {
return false;
}
// Remove hooks
for (const [hookName, hooks] of this.hooks.entries()) {
const filtered = hooks.filter(hook => hook.plugin !== name);
if (filtered.length === 0) {
this.hooks.delete(hookName);
} else {
this.hooks.set(hookName, filtered);
}
}
// Call cleanup if available
if (plugin.cleanup && typeof plugin.cleanup === 'function') {
try {
plugin.cleanup();
} catch (error) {
this.logger.error(`Plugin cleanup failed: ${name}`, { error });
}
}
this.plugins.delete(name);
this.logger.debug(`Plugin unloaded: ${name}`);
return true;
}
reloadPlugin(name) {
const plugin = this.plugins.get(name);
if (!plugin) {
return false;
}
const pluginPath = plugin.path;
this.unloadPlugin(name);
// Clear require cache
delete require.cache[require.resolve(pluginPath)];
return this.loadPlugin(pluginPath);
}
// Built-in hooks for extending LLM Checker functionality
getAvailableHooks() {
return [
'beforeHardwareDetection',
'afterHardwareDetection',
'beforeModelAnalysis',
'afterModelAnalysis',
'beforeOllamaOperation',
'afterOllamaOperation',
'beforeFormatOutput',
'afterFormatOutput',
'beforeGenerateReport',
'afterGenerateReport'
];
}
// Plugin development utilities
createPluginTemplate(name, type = 'code') {
const template = {
name: name,
version: '1.0.0',
description: `Plugin: ${name}`,
author: 'Your Name',
type: type
};
if (type === 'code') {
template.initialize = async function(pluginManager) {
// Plugin initialization code
console.log(`Plugin ${name} initialized`);
};
template.execute = async function(...args) {
// Plugin execution code
console.log(`Plugin ${name} executed with args:`, args);
return { success: true };
};
template.hooks = {
beforeModelAnalysis: async function(data, pluginManager) {
// Hook implementation
console.log(`${name} hook: beforeModelAnalysis`);
return data;
}
};
template.cleanup = function() {
// Cleanup code
console.log(`Plugin ${name} cleaned up`);
};
}
return template;
}
savePluginTemplate(name, outputPath, type = 'code') {
const template = this.createPluginTemplate(name, type);
const content = type === 'code' ?
`module.exports = ${JSON.stringify(template, null, 2)};` :
JSON.stringify(template, null, 2);
fs.writeFileSync(outputPath, content, 'utf8');
this.logger.info(`Plugin template created: ${outputPath}`);
}
}
module.exports = PluginManager;