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,359 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
class ConfigManager {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.configDir = path.join(os.homedir(), '.llm-checker');
|
|
8
|
+
this.configFile = path.join(this.configDir, 'config.json');
|
|
9
|
+
this.defaultConfig = this.getDefaultConfig();
|
|
10
|
+
this.config = null;
|
|
11
|
+
|
|
12
|
+
this.ensureConfigDirectory();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getDefaultConfig() {
|
|
16
|
+
return {
|
|
17
|
+
version: "2.0",
|
|
18
|
+
ollama: {
|
|
19
|
+
baseURL: process.env.OLLAMA_BASE_URL || "http://localhost:11434",
|
|
20
|
+
timeout: 30000,
|
|
21
|
+
enabled: true,
|
|
22
|
+
autoDetect: true,
|
|
23
|
+
retryAttempts: 3
|
|
24
|
+
},
|
|
25
|
+
analysis: {
|
|
26
|
+
includeCloudModels: false,
|
|
27
|
+
defaultUseCase: "general",
|
|
28
|
+
performanceTesting: false,
|
|
29
|
+
detailedHardwareInfo: false,
|
|
30
|
+
cacheResults: true,
|
|
31
|
+
cacheExpiry: 300000
|
|
32
|
+
},
|
|
33
|
+
display: {
|
|
34
|
+
maxModelsPerTable: 10,
|
|
35
|
+
showEmojis: !process.env.NO_COLOR,
|
|
36
|
+
colorOutput: !process.env.NO_COLOR,
|
|
37
|
+
compactMode: false,
|
|
38
|
+
showScores: true,
|
|
39
|
+
showInstallCommands: true
|
|
40
|
+
},
|
|
41
|
+
quantization: {
|
|
42
|
+
preferredLevel: "auto",
|
|
43
|
+
availableLevels: ["Q2_K", "Q3_K_M", "Q4_0", "Q4_K_M", "Q5_0", "Q5_K_M", "Q6_K", "Q8_0"],
|
|
44
|
+
hardwareBased: {
|
|
45
|
+
ultra_low: ["Q2_K", "Q3_K_M"],
|
|
46
|
+
low: ["Q4_0", "Q4_K_M"],
|
|
47
|
+
medium: ["Q4_K_M", "Q5_0"],
|
|
48
|
+
high: ["Q5_K_M", "Q6_K"],
|
|
49
|
+
ultra_high: ["Q8_0", "Q6_K"]
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
filters: {
|
|
53
|
+
excludeModels: [],
|
|
54
|
+
includeOnlyFrameworks: [],
|
|
55
|
+
excludeCategories: [],
|
|
56
|
+
minCompatibilityScore: 60,
|
|
57
|
+
maxModelSize: null,
|
|
58
|
+
yearFilter: null
|
|
59
|
+
},
|
|
60
|
+
hardware: {
|
|
61
|
+
overrides: {
|
|
62
|
+
ram: process.env.LLM_CHECKER_RAM_GB ? (isNaN(parseInt(process.env.LLM_CHECKER_RAM_GB)) ? null : parseInt(process.env.LLM_CHECKER_RAM_GB)) : null,
|
|
63
|
+
vram: process.env.LLM_CHECKER_VRAM_GB ? (isNaN(parseInt(process.env.LLM_CHECKER_VRAM_GB)) ? null : parseInt(process.env.LLM_CHECKER_VRAM_GB)) : null,
|
|
64
|
+
cpuCores: process.env.LLM_CHECKER_CPU_CORES ? (isNaN(parseInt(process.env.LLM_CHECKER_CPU_CORES)) ? null : parseInt(process.env.LLM_CHECKER_CPU_CORES)) : null,
|
|
65
|
+
architecture: process.env.LLM_CHECKER_ARCHITECTURE || null
|
|
66
|
+
},
|
|
67
|
+
ignoreIntegratedGPU: process.env.LLM_CHECKER_NO_GPU === 'true',
|
|
68
|
+
preferDedicatedGPU: true,
|
|
69
|
+
cacheHardwareInfo: true
|
|
70
|
+
},
|
|
71
|
+
recommendations: {
|
|
72
|
+
maxRecommendations: 5,
|
|
73
|
+
includeUpgradeSuggestions: true,
|
|
74
|
+
showPerformanceEstimates: true,
|
|
75
|
+
groupByCategory: true,
|
|
76
|
+
prioritizeOllamaSupport: true
|
|
77
|
+
},
|
|
78
|
+
logging: {
|
|
79
|
+
level: process.env.LLM_CHECKER_LOG_LEVEL || "info",
|
|
80
|
+
file: null,
|
|
81
|
+
enableDebug: process.env.DEBUG === '1',
|
|
82
|
+
enableVerbose: false,
|
|
83
|
+
saveReports: false,
|
|
84
|
+
reportsDirectory: path.join(os.homedir(), '.llm-checker', 'reports')
|
|
85
|
+
},
|
|
86
|
+
updates: {
|
|
87
|
+
checkForUpdates: true,
|
|
88
|
+
autoUpdateDatabase: true,
|
|
89
|
+
updateChannel: "stable",
|
|
90
|
+
notifyNewModels: true
|
|
91
|
+
},
|
|
92
|
+
customModels: []
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
ensureConfigDirectory() {
|
|
97
|
+
if (!fs.existsSync(this.configDir)) {
|
|
98
|
+
try {
|
|
99
|
+
fs.mkdirSync(this.configDir, { recursive: true });
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.warn(`Warning: Could not create config directory: ${error.message}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
loadConfig() {
|
|
107
|
+
if (this.config) {
|
|
108
|
+
return this.config;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let userConfig = {};
|
|
112
|
+
|
|
113
|
+
// Try to load user config file
|
|
114
|
+
if (fs.existsSync(this.configFile)) {
|
|
115
|
+
try {
|
|
116
|
+
const configContent = fs.readFileSync(this.configFile, 'utf8');
|
|
117
|
+
userConfig = JSON.parse(configContent);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.warn(`Warning: Could not parse config file: ${error.message}`);
|
|
120
|
+
console.warn('Using default configuration');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Merge with defaults
|
|
125
|
+
this.config = this.mergeConfigs(this.defaultConfig, userConfig);
|
|
126
|
+
|
|
127
|
+
// Apply environment variable overrides
|
|
128
|
+
this.applyEnvironmentOverrides();
|
|
129
|
+
|
|
130
|
+
return this.config;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
saveConfig(config = null) {
|
|
134
|
+
const configToSave = config || this.config || this.defaultConfig;
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const configContent = JSON.stringify(configToSave, null, 2);
|
|
138
|
+
fs.writeFileSync(this.configFile, configContent, 'utf8');
|
|
139
|
+
this.config = configToSave;
|
|
140
|
+
return true;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error(`Error saving config: ${error.message}`);
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
mergeConfigs(defaultConfig, userConfig) {
|
|
148
|
+
const merged = { ...defaultConfig };
|
|
149
|
+
|
|
150
|
+
for (const [key, value] of Object.entries(userConfig)) {
|
|
151
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
152
|
+
merged[key] = this.mergeConfigs(defaultConfig[key] || {}, value);
|
|
153
|
+
} else {
|
|
154
|
+
merged[key] = value;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return merged;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
applyEnvironmentOverrides() {
|
|
162
|
+
if (!this.config) return;
|
|
163
|
+
|
|
164
|
+
// Hardware overrides (validate parsed values are valid numbers)
|
|
165
|
+
if (process.env.LLM_CHECKER_RAM_GB) {
|
|
166
|
+
const parsed = parseInt(process.env.LLM_CHECKER_RAM_GB);
|
|
167
|
+
if (!isNaN(parsed) && parsed > 0) this.config.hardware.overrides.ram = parsed;
|
|
168
|
+
}
|
|
169
|
+
if (process.env.LLM_CHECKER_VRAM_GB) {
|
|
170
|
+
const parsed = parseInt(process.env.LLM_CHECKER_VRAM_GB);
|
|
171
|
+
if (!isNaN(parsed) && parsed > 0) this.config.hardware.overrides.vram = parsed;
|
|
172
|
+
}
|
|
173
|
+
if (process.env.LLM_CHECKER_CPU_CORES) {
|
|
174
|
+
const parsed = parseInt(process.env.LLM_CHECKER_CPU_CORES);
|
|
175
|
+
if (!isNaN(parsed) && parsed > 0) this.config.hardware.overrides.cpuCores = parsed;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Ollama overrides
|
|
179
|
+
if (process.env.OLLAMA_BASE_URL) {
|
|
180
|
+
this.config.ollama.baseURL = process.env.OLLAMA_BASE_URL;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Display overrides
|
|
184
|
+
if (process.env.NO_COLOR) {
|
|
185
|
+
this.config.display.colorOutput = false;
|
|
186
|
+
this.config.display.showEmojis = false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Debug overrides
|
|
190
|
+
if (process.env.DEBUG === '1') {
|
|
191
|
+
this.config.logging.enableDebug = true;
|
|
192
|
+
this.config.logging.level = 'debug';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// GPU overrides
|
|
196
|
+
if (process.env.LLM_CHECKER_NO_GPU === 'true') {
|
|
197
|
+
this.config.hardware.ignoreIntegratedGPU = true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
get(keyPath, defaultValue = null) {
|
|
202
|
+
const config = this.loadConfig();
|
|
203
|
+
const keys = keyPath.split('.');
|
|
204
|
+
let value = config;
|
|
205
|
+
|
|
206
|
+
for (const key of keys) {
|
|
207
|
+
if (value && typeof value === 'object' && key in value) {
|
|
208
|
+
value = value[key];
|
|
209
|
+
} else {
|
|
210
|
+
return defaultValue;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return value;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
set(keyPath, value) {
|
|
218
|
+
const config = this.loadConfig();
|
|
219
|
+
const keys = keyPath.split('.');
|
|
220
|
+
const lastKey = keys.pop();
|
|
221
|
+
|
|
222
|
+
let current = config;
|
|
223
|
+
for (const key of keys) {
|
|
224
|
+
if (!current[key] || typeof current[key] !== 'object') {
|
|
225
|
+
current[key] = {};
|
|
226
|
+
}
|
|
227
|
+
current = current[key];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
current[lastKey] = value;
|
|
231
|
+
this.saveConfig(config);
|
|
232
|
+
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
reset() {
|
|
237
|
+
this.config = null;
|
|
238
|
+
if (fs.existsSync(this.configFile)) {
|
|
239
|
+
try {
|
|
240
|
+
fs.unlinkSync(this.configFile);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.error(`Error removing config file: ${error.message}`);
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
exportConfig() {
|
|
250
|
+
const config = this.loadConfig();
|
|
251
|
+
return JSON.stringify(config, null, 2);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
importConfig(configString) {
|
|
255
|
+
try {
|
|
256
|
+
const config = JSON.parse(configString);
|
|
257
|
+
return this.saveConfig(config);
|
|
258
|
+
} catch (error) {
|
|
259
|
+
console.error(`Error importing config: ${error.message}`);
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
validateConfig(config = null) {
|
|
265
|
+
const configToValidate = config || this.loadConfig();
|
|
266
|
+
const errors = [];
|
|
267
|
+
|
|
268
|
+
// Validate required sections
|
|
269
|
+
const requiredSections = ['ollama', 'analysis', 'display', 'hardware'];
|
|
270
|
+
for (const section of requiredSections) {
|
|
271
|
+
if (!configToValidate[section]) {
|
|
272
|
+
errors.push(`Missing required section: ${section}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Validate Ollama URL
|
|
277
|
+
if (configToValidate.ollama?.baseURL) {
|
|
278
|
+
try {
|
|
279
|
+
new URL(configToValidate.ollama.baseURL);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
errors.push(`Invalid Ollama URL: ${configToValidate.ollama.baseURL}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Validate numeric values
|
|
286
|
+
if (configToValidate.ollama?.timeout && configToValidate.ollama.timeout < 1000) {
|
|
287
|
+
errors.push('Ollama timeout should be at least 1000ms');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (configToValidate.display?.maxModelsPerTable && configToValidate.display.maxModelsPerTable < 1) {
|
|
291
|
+
errors.push('maxModelsPerTable should be at least 1');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
valid: errors.length === 0,
|
|
296
|
+
errors
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
getConfigPath() {
|
|
301
|
+
return this.configFile;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
getConfigDirectory() {
|
|
305
|
+
return this.configDir;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
createBackup() {
|
|
309
|
+
if (!fs.existsSync(this.configFile)) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
314
|
+
const backupFile = path.join(this.configDir, `config.backup.${timestamp}.json`);
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
fs.copyFileSync(this.configFile, backupFile);
|
|
318
|
+
return backupFile;
|
|
319
|
+
} catch (error) {
|
|
320
|
+
console.error(`Error creating backup: ${error.message}`);
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
listBackups() {
|
|
326
|
+
try {
|
|
327
|
+
const files = fs.readdirSync(this.configDir);
|
|
328
|
+
return files
|
|
329
|
+
.filter(file => file.startsWith('config.backup.') && file.endsWith('.json'))
|
|
330
|
+
.map(file => ({
|
|
331
|
+
name: file,
|
|
332
|
+
path: path.join(this.configDir, file),
|
|
333
|
+
created: fs.statSync(path.join(this.configDir, file)).mtime
|
|
334
|
+
}))
|
|
335
|
+
.sort((a, b) => b.created - a.created);
|
|
336
|
+
} catch (error) {
|
|
337
|
+
console.error(`Error listing backups: ${error.message}`);
|
|
338
|
+
return [];
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
restoreBackup(backupName) {
|
|
343
|
+
const backupPath = path.join(this.configDir, backupName);
|
|
344
|
+
|
|
345
|
+
if (!fs.existsSync(backupPath)) {
|
|
346
|
+
throw new Error(`Backup file not found: ${backupName}`);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
fs.copyFileSync(backupPath, this.configFile);
|
|
351
|
+
this.config = null; // Force reload
|
|
352
|
+
return true;
|
|
353
|
+
} catch (error) {
|
|
354
|
+
throw new Error(`Error restoring backup: ${error.message}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
module.exports = ConfigManager;
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
class OutputFormatter {
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
this.useColors = options.colors !== false && !process.env.NO_COLOR;
|
|
6
|
+
this.useEmojis = options.emojis !== false && !process.env.NO_EMOJI;
|
|
7
|
+
this.compact = options.compact || false;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
formatSystemInfo(hardware) {
|
|
11
|
+
const lines = [];
|
|
12
|
+
|
|
13
|
+
if (!this.compact) {
|
|
14
|
+
lines.push(this.header('System Information'));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
lines.push(this.info('CPU', `${hardware.cpu.brand} (${hardware.cpu.cores} cores, ${hardware.cpu.speed || 'Unknown'}GHz)`));
|
|
18
|
+
lines.push(this.info('Architecture', hardware.cpu.architecture));
|
|
19
|
+
lines.push(this.info('RAM', `${hardware.memory.total}GB total (${hardware.memory.free}GB free, ${hardware.memory.usagePercent}% used)`));
|
|
20
|
+
lines.push(this.info('GPU', hardware.gpu.model || 'Not detected'));
|
|
21
|
+
const vramDisplay = hardware.gpu.vram === 0 && hardware.gpu.model && hardware.gpu.model.toLowerCase().includes('apple')
|
|
22
|
+
? 'Unified Memory'
|
|
23
|
+
: `${hardware.gpu.vram || 'N/A'}GB`;
|
|
24
|
+
lines.push(this.info('VRAM', `${vramDisplay}${hardware.gpu.dedicated ? ' (Dedicated)' : ' (Integrated)'}`));
|
|
25
|
+
lines.push(this.info('OS', `${hardware.os.distro} ${hardware.os.release} (${hardware.os.arch})`));
|
|
26
|
+
|
|
27
|
+
return lines.join('\n');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
formatCompatibilityResults(results) {
|
|
31
|
+
const lines = [];
|
|
32
|
+
|
|
33
|
+
if (results.compatible.length > 0) {
|
|
34
|
+
lines.push(this.success('Compatible Models (Score ≥ 75)'));
|
|
35
|
+
lines.push(this.formatModelsTable(results.compatible, 'compatible'));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (results.marginal.length > 0) {
|
|
39
|
+
lines.push(this.warning('Marginal Performance (Score 60-74)'));
|
|
40
|
+
lines.push(this.formatModelsTable(results.marginal, 'marginal'));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (results.incompatible.length > 0 && !this.compact) {
|
|
44
|
+
lines.push(this.error('Incompatible Models (showing top 5)'));
|
|
45
|
+
lines.push(this.formatModelsTable(results.incompatible.slice(0, 5), 'incompatible'));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return lines.join('\n\n');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
formatModelsTable(models, type) {
|
|
52
|
+
if (models.length === 0) return '';
|
|
53
|
+
|
|
54
|
+
const headers = ['Model', 'Size', 'Score', 'RAM', 'VRAM', 'Speed'];
|
|
55
|
+
const rows = [headers];
|
|
56
|
+
|
|
57
|
+
models.slice(0, this.compact ? 5 : 10).forEach(model => {
|
|
58
|
+
const scoreText = `${model.score || 0}/100`;
|
|
59
|
+
const scoreColored = this.scoreColor(model.score || 0, scoreText);
|
|
60
|
+
|
|
61
|
+
rows.push([
|
|
62
|
+
this.truncate(model.name, 20),
|
|
63
|
+
model.size || 'Unknown',
|
|
64
|
+
scoreColored,
|
|
65
|
+
`${model.requirements?.ram || '?'}GB`,
|
|
66
|
+
`${model.requirements?.vram || 0}GB`,
|
|
67
|
+
this.formatSpeed(model.performance?.speed)
|
|
68
|
+
]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return this.createTable(rows);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
formatRecommendations(recommendations) {
|
|
75
|
+
if (!recommendations || recommendations.length === 0) {
|
|
76
|
+
return '';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const lines = [this.header('Recommendations')];
|
|
80
|
+
|
|
81
|
+
recommendations.forEach((rec, index) => {
|
|
82
|
+
lines.push(`${index + 1}. ${rec}`);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return lines.join('\n');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
formatOllamaStatus(ollamaInfo) {
|
|
89
|
+
if (!ollamaInfo) return '';
|
|
90
|
+
|
|
91
|
+
const status = ollamaInfo.available ?
|
|
92
|
+
this.success(`Running (v${ollamaInfo.version || 'unknown'})`) :
|
|
93
|
+
this.error(`${ollamaInfo.error || 'Not available'}`);
|
|
94
|
+
|
|
95
|
+
let result = this.info('Ollama Status', status);
|
|
96
|
+
|
|
97
|
+
if (ollamaInfo.available) {
|
|
98
|
+
result += '\n' + this.info('Local Models', `${ollamaInfo.localModels || 0} installed`);
|
|
99
|
+
if (ollamaInfo.runningModels > 0) {
|
|
100
|
+
result += '\n' + this.info('Running Models', ollamaInfo.runningModels);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
formatHardwareTier(tier, score) {
|
|
108
|
+
const tierFormatted = tier.replace('_', ' ').toUpperCase();
|
|
109
|
+
const tierColored = this.tierColor(tier, tierFormatted);
|
|
110
|
+
return this.info('Hardware Tier', `${tierColored} (Overall Score: ${score}/100)`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
formatPerformanceBenchmark(benchmark) {
|
|
114
|
+
if (!benchmark) return '';
|
|
115
|
+
|
|
116
|
+
const lines = [this.header('Performance Benchmark')];
|
|
117
|
+
lines.push(this.info('CPU Score', `${benchmark.cpu}/100`));
|
|
118
|
+
lines.push(this.info('Memory Score', `${benchmark.memory}/100`));
|
|
119
|
+
lines.push(this.info('Overall Score', `${benchmark.overall}/100`));
|
|
120
|
+
|
|
121
|
+
return lines.join('\n');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
formatInstallCommands(commands) {
|
|
125
|
+
if (!commands || commands.length === 0) return '';
|
|
126
|
+
|
|
127
|
+
const lines = [this.header('Quick Install Commands')];
|
|
128
|
+
|
|
129
|
+
commands.forEach(cmd => {
|
|
130
|
+
const status = cmd.isInstalled ?
|
|
131
|
+
this.success('Installed') :
|
|
132
|
+
this.dim('Not installed');
|
|
133
|
+
lines.push(`${status} ${this.highlight(cmd.command)}`);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return lines.join('\n');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
formatUpgradeSuggestions(suggestions) {
|
|
140
|
+
if (!suggestions || suggestions.length === 0) return '';
|
|
141
|
+
|
|
142
|
+
const lines = [this.header('Hardware Upgrade Suggestions')];
|
|
143
|
+
|
|
144
|
+
suggestions.forEach((suggestion, index) => {
|
|
145
|
+
lines.push(`${index + 1}. ${suggestion}`);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
return lines.join('\n');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
formatNextSteps(steps) {
|
|
152
|
+
if (!steps || steps.length === 0) return '';
|
|
153
|
+
|
|
154
|
+
const lines = [this.header('Next Steps')];
|
|
155
|
+
|
|
156
|
+
steps.forEach((step, index) => {
|
|
157
|
+
lines.push(`${index + 1}. ${step}`);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return lines.join('\n');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Helper methods for styling
|
|
164
|
+
header(text) {
|
|
165
|
+
return this.useColors ? chalk.blue.bold(text) : text;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
success(text) {
|
|
169
|
+
return this.useColors ? chalk.green(text) : text;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
warning(text) {
|
|
173
|
+
return this.useColors ? chalk.yellow(text) : text;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
error(text) {
|
|
177
|
+
return this.useColors ? chalk.red(text) : text;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
info(label, value) {
|
|
181
|
+
const labelFormatted = this.useColors ? chalk.cyan(label + ':') : label + ':';
|
|
182
|
+
return `${labelFormatted} ${value}`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
highlight(text) {
|
|
186
|
+
return this.useColors ? chalk.cyan(text) : text;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
dim(text) {
|
|
190
|
+
return this.useColors ? chalk.gray(text) : text;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
scoreColor(score, text) {
|
|
194
|
+
if (!this.useColors) return text;
|
|
195
|
+
|
|
196
|
+
if (score >= 90) return chalk.green(text);
|
|
197
|
+
if (score >= 75) return chalk.yellow(text);
|
|
198
|
+
if (score >= 60) return chalk.orange(text);
|
|
199
|
+
return chalk.red(text);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
tierColor(tier, text) {
|
|
203
|
+
if (!this.useColors) return text;
|
|
204
|
+
|
|
205
|
+
switch (tier) {
|
|
206
|
+
case 'ultra_high': return chalk.magenta(text);
|
|
207
|
+
case 'high': return chalk.green(text);
|
|
208
|
+
case 'medium': return chalk.yellow(text);
|
|
209
|
+
case 'low': return chalk.orange(text);
|
|
210
|
+
case 'ultra_low': return chalk.red(text);
|
|
211
|
+
default: return text;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
formatSpeed(speed) {
|
|
216
|
+
const speedMap = {
|
|
217
|
+
'very_fast': 'Very Fast',
|
|
218
|
+
'fast': 'Fast',
|
|
219
|
+
'medium': 'Medium',
|
|
220
|
+
'slow': 'Slow',
|
|
221
|
+
'very_slow': 'Very Slow'
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
return speedMap[speed] || speed || 'Unknown';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
truncate(text, maxLength) {
|
|
228
|
+
if (!text) return '';
|
|
229
|
+
return text.length > maxLength ? text.substring(0, maxLength - 3) + '...' : text;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
createTable(rows) {
|
|
233
|
+
if (rows.length === 0) return '';
|
|
234
|
+
|
|
235
|
+
// Calculate column widths
|
|
236
|
+
const widths = rows[0].map((_, colIndex) =>
|
|
237
|
+
Math.max(...rows.map(row => this.stripAnsi(row[colIndex] || '').length))
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
const lines = [];
|
|
241
|
+
|
|
242
|
+
rows.forEach((row, rowIndex) => {
|
|
243
|
+
const paddedRow = row.map((cell, colIndex) => {
|
|
244
|
+
const cellText = cell || '';
|
|
245
|
+
const padding = widths[colIndex] - this.stripAnsi(cellText).length;
|
|
246
|
+
return cellText + ' '.repeat(Math.max(0, padding));
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
lines.push('│ ' + paddedRow.join(' │ ') + ' │');
|
|
250
|
+
|
|
251
|
+
// Add separator after header
|
|
252
|
+
if (rowIndex === 0) {
|
|
253
|
+
const separator = '├' + widths.map(w => '─'.repeat(w + 2)).join('┼') + '┤';
|
|
254
|
+
lines.push(separator);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Add top and bottom borders
|
|
259
|
+
const topBorder = '┌' + widths.map(w => '─'.repeat(w + 2)).join('┬') + '┐';
|
|
260
|
+
const bottomBorder = '└' + widths.map(w => '─'.repeat(w + 2)).join('┴') + '┘';
|
|
261
|
+
|
|
262
|
+
return [topBorder, ...lines, bottomBorder].join('\n');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
stripAnsi(text) {
|
|
266
|
+
// Simple ANSI escape code removal
|
|
267
|
+
return text.replace(/\x1b\[[0-9;]*m/g, '');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
formatJSON(data, pretty = true) {
|
|
271
|
+
return JSON.stringify(data, null, pretty ? 2 : 0);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
formatCSV(data, headers) {
|
|
275
|
+
const lines = [];
|
|
276
|
+
|
|
277
|
+
if (headers) {
|
|
278
|
+
lines.push(headers.join(','));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
data.forEach(row => {
|
|
282
|
+
const csvRow = row.map(cell => {
|
|
283
|
+
const cellStr = String(cell || '');
|
|
284
|
+
// Escape quotes and wrap in quotes if necessary
|
|
285
|
+
if (cellStr.includes(',') || cellStr.includes('"') || cellStr.includes('\n')) {
|
|
286
|
+
return '"' + cellStr.replace(/"/g, '""') + '"';
|
|
287
|
+
}
|
|
288
|
+
return cellStr;
|
|
289
|
+
});
|
|
290
|
+
lines.push(csvRow.join(','));
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
return lines.join('\n');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
formatMarkdown(data) {
|
|
297
|
+
// Simple markdown table formatting
|
|
298
|
+
if (!data || data.length === 0) return '';
|
|
299
|
+
|
|
300
|
+
const headers = data[0];
|
|
301
|
+
const rows = data.slice(1);
|
|
302
|
+
|
|
303
|
+
const lines = [];
|
|
304
|
+
lines.push('| ' + headers.join(' | ') + ' |');
|
|
305
|
+
lines.push('| ' + headers.map(() => '---').join(' | ') + ' |');
|
|
306
|
+
|
|
307
|
+
rows.forEach(row => {
|
|
308
|
+
lines.push('| ' + row.join(' | ') + ' |');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
return lines.join('\n');
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
module.exports = OutputFormatter;
|