llm-checker 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +418 -0
- package/analyzer/compatibility.js +584 -0
- package/analyzer/performance.js +505 -0
- package/bin/CLAUDE.md +12 -0
- package/bin/enhanced_cli.js +3118 -0
- package/bin/test-deterministic.js +41 -0
- package/package.json +96 -0
- package/src/CLAUDE.md +12 -0
- package/src/ai/intelligent-selector.js +615 -0
- package/src/ai/model-selector.js +312 -0
- package/src/ai/multi-objective-selector.js +820 -0
- package/src/commands/check.js +58 -0
- package/src/data/CLAUDE.md +11 -0
- package/src/data/model-database.js +637 -0
- package/src/data/sync-manager.js +279 -0
- package/src/hardware/CLAUDE.md +12 -0
- package/src/hardware/backends/CLAUDE.md +11 -0
- package/src/hardware/backends/apple-silicon.js +318 -0
- package/src/hardware/backends/cpu-detector.js +490 -0
- package/src/hardware/backends/cuda-detector.js +417 -0
- package/src/hardware/backends/intel-detector.js +436 -0
- package/src/hardware/backends/rocm-detector.js +440 -0
- package/src/hardware/detector.js +573 -0
- package/src/hardware/pc-optimizer.js +635 -0
- package/src/hardware/specs.js +286 -0
- package/src/hardware/unified-detector.js +442 -0
- package/src/index.js +2289 -0
- package/src/models/CLAUDE.md +17 -0
- package/src/models/ai-check-selector.js +806 -0
- package/src/models/catalog.json +426 -0
- package/src/models/deterministic-selector.js +1145 -0
- package/src/models/expanded_database.js +1142 -0
- package/src/models/intelligent-selector.js +532 -0
- package/src/models/requirements.js +310 -0
- package/src/models/scoring-config.js +57 -0
- package/src/models/scoring-engine.js +715 -0
- package/src/ollama/.cache/README.md +33 -0
- package/src/ollama/CLAUDE.md +24 -0
- package/src/ollama/client.js +438 -0
- package/src/ollama/enhanced-client.js +113 -0
- package/src/ollama/enhanced-scraper.js +634 -0
- package/src/ollama/manager.js +357 -0
- package/src/ollama/native-scraper.js +776 -0
- package/src/plugins/CLAUDE.md +11 -0
- package/src/plugins/examples/custom_model_plugin.js +87 -0
- package/src/plugins/index.js +295 -0
- package/src/utils/CLAUDE.md +11 -0
- package/src/utils/config.js +359 -0
- package/src/utils/formatter.js +315 -0
- package/src/utils/logger.js +272 -0
- package/src/utils/model-classifier.js +167 -0
- package/src/utils/verbose-progress.js +266 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<claude-mem-context>
|
|
2
|
+
# Recent Activity
|
|
3
|
+
|
|
4
|
+
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
|
5
|
+
|
|
6
|
+
### Feb 12, 2026
|
|
7
|
+
|
|
8
|
+
| ID | Time | T | Title | Read |
|
|
9
|
+
|----|------|---|-------|------|
|
|
10
|
+
| #3462 | 10:02 PM | 🔵 | Plugin System Architecture - Hook-Based Extensibility Framework | ~648 |
|
|
11
|
+
</claude-mem-context>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
name: 'custom-model-plugin',
|
|
3
|
+
version: '1.0.0',
|
|
4
|
+
description: 'Adds custom models to the LLM Checker database',
|
|
5
|
+
author: 'LLM Checker Team',
|
|
6
|
+
type: 'code',
|
|
7
|
+
|
|
8
|
+
// Custom models to add
|
|
9
|
+
customModels: [
|
|
10
|
+
{
|
|
11
|
+
name: "CustomLlama 5B",
|
|
12
|
+
size: "5B",
|
|
13
|
+
type: "local",
|
|
14
|
+
category: "medium",
|
|
15
|
+
requirements: {
|
|
16
|
+
ram: 6,
|
|
17
|
+
vram: 3,
|
|
18
|
+
cpu_cores: 4,
|
|
19
|
+
storage: 5
|
|
20
|
+
},
|
|
21
|
+
frameworks: ["ollama", "llama.cpp"],
|
|
22
|
+
quantization: ["Q4_0", "Q4_K_M", "Q5_0"],
|
|
23
|
+
performance: {
|
|
24
|
+
speed: "medium",
|
|
25
|
+
quality: "very_good",
|
|
26
|
+
context_length: 8192
|
|
27
|
+
},
|
|
28
|
+
installation: {
|
|
29
|
+
ollama: "ollama pull custom-llama:5b",
|
|
30
|
+
description: "Custom trained Llama model"
|
|
31
|
+
},
|
|
32
|
+
specialization: "custom",
|
|
33
|
+
languages: ["en"],
|
|
34
|
+
year: 2024
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
|
|
38
|
+
async initialize(pluginManager) {
|
|
39
|
+
pluginManager.logger.info('Custom Model Plugin initialized');
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
hooks: {
|
|
43
|
+
afterModelAnalysis: async function(data, pluginManager) {
|
|
44
|
+
// Add custom models to the analysis results
|
|
45
|
+
if (data.models) {
|
|
46
|
+
data.models.push(...this.customModels);
|
|
47
|
+
pluginManager.logger.debug(`Added ${this.customModels.length} custom models`);
|
|
48
|
+
}
|
|
49
|
+
return data;
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
beforeGenerateReport: async function(data, pluginManager) {
|
|
53
|
+
// Add note about custom models in reports
|
|
54
|
+
if (data.recommendations) {
|
|
55
|
+
data.recommendations.push('Custom models available via plugin');
|
|
56
|
+
}
|
|
57
|
+
return data;
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
async execute(action, ...args) {
|
|
62
|
+
switch (action) {
|
|
63
|
+
case 'listCustomModels':
|
|
64
|
+
return this.customModels;
|
|
65
|
+
|
|
66
|
+
case 'addCustomModel':
|
|
67
|
+
const [model] = args;
|
|
68
|
+
if (this.validateCustomModel(model)) {
|
|
69
|
+
this.customModels.push(model);
|
|
70
|
+
return { success: true, message: 'Custom model added' };
|
|
71
|
+
}
|
|
72
|
+
return { success: false, message: 'Invalid model format' };
|
|
73
|
+
|
|
74
|
+
default:
|
|
75
|
+
return { success: false, message: 'Unknown action' };
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
validateCustomModel(model) {
|
|
80
|
+
const required = ['name', 'size', 'type', 'category', 'requirements'];
|
|
81
|
+
return required.every(field => model.hasOwnProperty(field));
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
cleanup() {
|
|
85
|
+
console.log('Custom Model Plugin cleaned up');
|
|
86
|
+
}
|
|
87
|
+
};
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { getLogger } = require('../utils/logger');
|
|
4
|
+
|
|
5
|
+
class PluginManager {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.pluginsDir = options.pluginsDir || path.join(process.cwd(), 'plugins');
|
|
8
|
+
this.plugins = new Map();
|
|
9
|
+
this.hooks = new Map();
|
|
10
|
+
this.logger = getLogger().createChild('PluginManager');
|
|
11
|
+
this.enabled = options.enabled !== false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async loadPlugins() {
|
|
15
|
+
if (!this.enabled) {
|
|
16
|
+
this.logger.debug('Plugin system disabled');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!fs.existsSync(this.pluginsDir)) {
|
|
21
|
+
this.logger.debug('Plugins directory not found, skipping plugin loading');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const pluginFiles = fs.readdirSync(this.pluginsDir)
|
|
27
|
+
.filter(file => file.endsWith('.js') || file.endsWith('.json'));
|
|
28
|
+
|
|
29
|
+
for (const file of pluginFiles) {
|
|
30
|
+
await this.loadPlugin(path.join(this.pluginsDir, file));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this.logger.info(`Loaded ${this.plugins.size} plugins`);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
this.logger.error('Failed to load plugins', { error });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async loadPlugin(pluginPath) {
|
|
40
|
+
try {
|
|
41
|
+
const pluginName = path.basename(pluginPath, path.extname(pluginPath));
|
|
42
|
+
|
|
43
|
+
// Security: Ensure plugin path is within the plugins directory
|
|
44
|
+
const resolvedPath = path.resolve(pluginPath);
|
|
45
|
+
const resolvedPluginsDir = path.resolve(this.pluginsDir);
|
|
46
|
+
if (!resolvedPath.startsWith(resolvedPluginsDir + path.sep)) {
|
|
47
|
+
this.logger.warn(`Blocked plugin outside plugins directory: ${pluginPath}`);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Load plugin configuration or code
|
|
52
|
+
// WARNING: Code plugins execute with full Node.js permissions.
|
|
53
|
+
// Only load plugins from trusted sources.
|
|
54
|
+
let plugin;
|
|
55
|
+
if (pluginPath.endsWith('.json')) {
|
|
56
|
+
plugin = JSON.parse(fs.readFileSync(pluginPath, 'utf8'));
|
|
57
|
+
plugin.type = 'config';
|
|
58
|
+
} else {
|
|
59
|
+
plugin = require(resolvedPath);
|
|
60
|
+
plugin.type = 'code';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Validate plugin
|
|
64
|
+
if (!this.validatePlugin(plugin)) {
|
|
65
|
+
this.logger.warn(`Invalid plugin: ${pluginName}`);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Initialize plugin
|
|
70
|
+
if (plugin.initialize && typeof plugin.initialize === 'function') {
|
|
71
|
+
await plugin.initialize(this);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Register plugin
|
|
75
|
+
this.plugins.set(pluginName, {
|
|
76
|
+
...plugin,
|
|
77
|
+
name: pluginName,
|
|
78
|
+
path: pluginPath,
|
|
79
|
+
loaded: true
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Register hooks
|
|
83
|
+
if (plugin.hooks) {
|
|
84
|
+
this.registerHooks(pluginName, plugin.hooks);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.logger.debug(`Plugin loaded: ${pluginName}`, {
|
|
88
|
+
data: { type: plugin.type, version: plugin.version }
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return true;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
this.logger.error(`Failed to load plugin: ${pluginPath}`, { error });
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
validatePlugin(plugin) {
|
|
99
|
+
// Basic plugin validation
|
|
100
|
+
if (!plugin.name || !plugin.version) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (plugin.type === 'code' && !plugin.execute && !plugin.hooks) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
registerHooks(pluginName, hooks) {
|
|
112
|
+
for (const [hookName, hookFunction] of Object.entries(hooks)) {
|
|
113
|
+
if (!this.hooks.has(hookName)) {
|
|
114
|
+
this.hooks.set(hookName, []);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.hooks.get(hookName).push({
|
|
118
|
+
plugin: pluginName,
|
|
119
|
+
function: hookFunction
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async executeHook(hookName, data = {}) {
|
|
125
|
+
if (!this.hooks.has(hookName)) {
|
|
126
|
+
return data;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let result = data;
|
|
130
|
+
const hookFunctions = this.hooks.get(hookName);
|
|
131
|
+
|
|
132
|
+
for (const hook of hookFunctions) {
|
|
133
|
+
try {
|
|
134
|
+
this.logger.trace(`Executing hook: ${hookName} from plugin: ${hook.plugin}`);
|
|
135
|
+
result = await hook.function(result, this) || result;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
this.logger.error(`Hook execution failed`, {
|
|
138
|
+
data: { hook: hookName, plugin: hook.plugin },
|
|
139
|
+
error
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async executePlugin(pluginName, ...args) {
|
|
148
|
+
const plugin = this.plugins.get(pluginName);
|
|
149
|
+
|
|
150
|
+
if (!plugin) {
|
|
151
|
+
throw new Error(`Plugin not found: ${pluginName}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!plugin.execute || typeof plugin.execute !== 'function') {
|
|
155
|
+
throw new Error(`Plugin ${pluginName} is not executable`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
this.logger.debug(`Executing plugin: ${pluginName}`);
|
|
160
|
+
return await plugin.execute(...args);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
this.logger.error(`Plugin execution failed: ${pluginName}`, { error });
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
getPlugin(name) {
|
|
168
|
+
return this.plugins.get(name);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
listPlugins() {
|
|
172
|
+
return Array.from(this.plugins.values()).map(plugin => ({
|
|
173
|
+
name: plugin.name,
|
|
174
|
+
version: plugin.version,
|
|
175
|
+
description: plugin.description,
|
|
176
|
+
type: plugin.type,
|
|
177
|
+
loaded: plugin.loaded
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
unloadPlugin(name) {
|
|
182
|
+
const plugin = this.plugins.get(name);
|
|
183
|
+
|
|
184
|
+
if (!plugin) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Remove hooks
|
|
189
|
+
for (const [hookName, hooks] of this.hooks.entries()) {
|
|
190
|
+
const filtered = hooks.filter(hook => hook.plugin !== name);
|
|
191
|
+
if (filtered.length === 0) {
|
|
192
|
+
this.hooks.delete(hookName);
|
|
193
|
+
} else {
|
|
194
|
+
this.hooks.set(hookName, filtered);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Call cleanup if available
|
|
199
|
+
if (plugin.cleanup && typeof plugin.cleanup === 'function') {
|
|
200
|
+
try {
|
|
201
|
+
plugin.cleanup();
|
|
202
|
+
} catch (error) {
|
|
203
|
+
this.logger.error(`Plugin cleanup failed: ${name}`, { error });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
this.plugins.delete(name);
|
|
208
|
+
this.logger.debug(`Plugin unloaded: ${name}`);
|
|
209
|
+
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
reloadPlugin(name) {
|
|
214
|
+
const plugin = this.plugins.get(name);
|
|
215
|
+
|
|
216
|
+
if (!plugin) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const pluginPath = plugin.path;
|
|
221
|
+
this.unloadPlugin(name);
|
|
222
|
+
|
|
223
|
+
// Clear require cache
|
|
224
|
+
delete require.cache[require.resolve(pluginPath)];
|
|
225
|
+
|
|
226
|
+
return this.loadPlugin(pluginPath);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Built-in hooks for extending LLM Checker functionality
|
|
230
|
+
getAvailableHooks() {
|
|
231
|
+
return [
|
|
232
|
+
'beforeHardwareDetection',
|
|
233
|
+
'afterHardwareDetection',
|
|
234
|
+
'beforeModelAnalysis',
|
|
235
|
+
'afterModelAnalysis',
|
|
236
|
+
'beforeOllamaOperation',
|
|
237
|
+
'afterOllamaOperation',
|
|
238
|
+
'beforeFormatOutput',
|
|
239
|
+
'afterFormatOutput',
|
|
240
|
+
'beforeGenerateReport',
|
|
241
|
+
'afterGenerateReport'
|
|
242
|
+
];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Plugin development utilities
|
|
246
|
+
createPluginTemplate(name, type = 'code') {
|
|
247
|
+
const template = {
|
|
248
|
+
name: name,
|
|
249
|
+
version: '1.0.0',
|
|
250
|
+
description: `Plugin: ${name}`,
|
|
251
|
+
author: 'Your Name',
|
|
252
|
+
type: type
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
if (type === 'code') {
|
|
256
|
+
template.initialize = async function(pluginManager) {
|
|
257
|
+
// Plugin initialization code
|
|
258
|
+
console.log(`Plugin ${name} initialized`);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
template.execute = async function(...args) {
|
|
262
|
+
// Plugin execution code
|
|
263
|
+
console.log(`Plugin ${name} executed with args:`, args);
|
|
264
|
+
return { success: true };
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
template.hooks = {
|
|
268
|
+
beforeModelAnalysis: async function(data, pluginManager) {
|
|
269
|
+
// Hook implementation
|
|
270
|
+
console.log(`${name} hook: beforeModelAnalysis`);
|
|
271
|
+
return data;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
template.cleanup = function() {
|
|
276
|
+
// Cleanup code
|
|
277
|
+
console.log(`Plugin ${name} cleaned up`);
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return template;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
savePluginTemplate(name, outputPath, type = 'code') {
|
|
285
|
+
const template = this.createPluginTemplate(name, type);
|
|
286
|
+
const content = type === 'code' ?
|
|
287
|
+
`module.exports = ${JSON.stringify(template, null, 2)};` :
|
|
288
|
+
JSON.stringify(template, null, 2);
|
|
289
|
+
|
|
290
|
+
fs.writeFileSync(outputPath, content, 'utf8');
|
|
291
|
+
this.logger.info(`Plugin template created: ${outputPath}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
module.exports = PluginManager;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<claude-mem-context>
|
|
2
|
+
# Recent Activity
|
|
3
|
+
|
|
4
|
+
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
|
5
|
+
|
|
6
|
+
### Feb 12, 2026
|
|
7
|
+
|
|
8
|
+
| ID | Time | T | Title | Read |
|
|
9
|
+
|----|------|---|-------|------|
|
|
10
|
+
| #3438 | 9:58 PM | 🔵 | Configuration Management System - Comprehensive Settings with Environment Overrides | ~580 |
|
|
11
|
+
</claude-mem-context>
|