llm-checker 3.5.12 → 3.5.13
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/README.md +83 -17
- package/bin/cli.js +40 -0
- package/bin/enhanced_cli.js +360 -33
- package/package.json +2 -1
- package/src/ai/model-selector.js +47 -16
- package/src/ai/multi-objective-selector.js +55 -9
- package/src/data/model-database.js +92 -1
- package/src/data/seed/README.md +8 -0
- package/src/data/seed/models.db +0 -0
- package/src/hardware/backends/rocm-detector.js +469 -68
- package/src/hardware/unified-detector.js +39 -5
- package/src/index.js +40 -7
- package/src/models/ai-check-selector.js +27 -2
- package/src/models/deterministic-selector.js +80 -7
- package/src/ollama/client.js +121 -0
- package/src/ollama/enhanced-scraper.js +40 -26
- package/src/ollama/native-scraper.js +52 -27
- package/src/ui/cli-theme.js +139 -24
- package/src/ui/interactive-panel.js +1 -18
- package/src/utils/verbose-progress.js +144 -187
|
@@ -83,7 +83,7 @@ class OllamaNativeScraper {
|
|
|
83
83
|
|
|
84
84
|
parseModelFromHTML(html) {
|
|
85
85
|
const models = [];
|
|
86
|
-
const pattern = /<a[^>]*href="\/library\/([^"]*)"[^>]*>[\s\S]{0,5000}?<h3[^>]*>([^<]*)<\/h3>[\s\S]{0,2000}?<p[^>]*>([^<]*)<\/p>[\s\S]{0,2000}?(?:<span[^>]*>([^<]*)<\/span>)[\s\S]{0,2000}?(?:(\d+(?:\.\d+)?[KMB]?)\s*(?:Pulls|
|
|
86
|
+
const pattern = /<a[^>]*href="\/library\/([^"]*)"[^>]*>[\s\S]{0,5000}?<h3[^>]*>([^<]*)<\/h3>[\s\S]{0,2000}?<p[^>]*>([^<]*)<\/p>[\s\S]{0,2000}?(?:<span[^>]*>([^<]*)<\/span>)[\s\S]{0,2000}?(?:(\d+(?:\.\d+)?[KMB]?)\s*(?:Pulls|Downloads))[\s\S]{0,1000}?(?:(\d+)\s*(?:Tags|Models|tags|models))[\s\S]{0,1000}?(?:Updated\s*(\d+\s*\w+\s*ago))?[\s\S]{0,500}?<\/a>/gi;
|
|
87
87
|
|
|
88
88
|
let match;
|
|
89
89
|
while ((match = pattern.exec(html)) !== null) {
|
|
@@ -126,16 +126,18 @@ class OllamaNativeScraper {
|
|
|
126
126
|
const section = html.substring(Math.max(0, linkIndex - 500), linkIndex + 500);
|
|
127
127
|
const nameMatch = section.match(/<h[2-4][^>]*>([^<]*)<\/h[2-4]>/);
|
|
128
128
|
const descMatch = section.match(/<p[^>]*>([^<]*)<\/p>/);
|
|
129
|
-
const
|
|
129
|
+
const sectionText = this.htmlToText(section);
|
|
130
|
+
const pullsMatch = sectionText.match(/(\d+(?:\.\d+)?\s*[KMB]?)\s*(?:Pulls|Downloads)/i);
|
|
131
|
+
const updatedMatch = sectionText.match(/Updated\s+(\d+\s*(?:minutes?|hours?|days?|weeks?|months?|years?)\s+ago)/i);
|
|
130
132
|
|
|
131
133
|
models.push({
|
|
132
134
|
model_identifier: identifier,
|
|
133
135
|
model_name: nameMatch ? this.cleanText(nameMatch[1]) : identifier,
|
|
134
136
|
description: descMatch ? this.cleanText(descMatch[1]) : '',
|
|
135
137
|
labels: [],
|
|
136
|
-
pulls: pullsMatch ? this.parsePulls(pullsMatch[1]) : 0,
|
|
138
|
+
pulls: pullsMatch ? this.parsePulls(pullsMatch[1].replace(/\s+/g, '')) : 0,
|
|
137
139
|
tags: 0,
|
|
138
|
-
last_updated: 'Unknown',
|
|
140
|
+
last_updated: updatedMatch ? updatedMatch[1].replace(/\s+/g, ' ').trim() : 'Unknown',
|
|
139
141
|
url: `${this.baseURL}/library/${identifier}`,
|
|
140
142
|
namespace: identifier.includes('/') ? identifier.split('/')[0] : null,
|
|
141
143
|
model_type: identifier.includes('/') ? 'community' : 'official'
|
|
@@ -147,16 +149,25 @@ class OllamaNativeScraper {
|
|
|
147
149
|
}
|
|
148
150
|
|
|
149
151
|
cleanText(text) {
|
|
150
|
-
return text
|
|
152
|
+
return String(text || '')
|
|
153
|
+
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
|
|
154
|
+
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
|
|
155
|
+
.replace(/<[^>]+>/g, ' ')
|
|
156
|
+
.replace(/ | /g, ' ')
|
|
151
157
|
.replace(/&/g, '&')
|
|
152
158
|
.replace(/</g, '<')
|
|
153
159
|
.replace(/>/g, '>')
|
|
154
160
|
.replace(/"/g, '"')
|
|
161
|
+
.replace(/'/g, "'")
|
|
155
162
|
.replace(/'/g, "'")
|
|
156
163
|
.replace(/\s+/g, ' ')
|
|
157
164
|
.trim();
|
|
158
165
|
}
|
|
159
166
|
|
|
167
|
+
htmlToText(html) {
|
|
168
|
+
return this.cleanText(html);
|
|
169
|
+
}
|
|
170
|
+
|
|
160
171
|
parsePulls(pullsStr) {
|
|
161
172
|
if (!pullsStr) return 0;
|
|
162
173
|
const num = parseFloat(pullsStr);
|
|
@@ -167,6 +178,20 @@ class OllamaNativeScraper {
|
|
|
167
178
|
return Math.floor(num);
|
|
168
179
|
}
|
|
169
180
|
|
|
181
|
+
extractPageSummary(html) {
|
|
182
|
+
const text = this.htmlToText(html);
|
|
183
|
+
const downloadsMatch = text.match(/(\d+(?:\.\d+)?\s*[KMB]?)\s*(?:Downloads|Pulls)\b/i);
|
|
184
|
+
const updatedMatch = text.match(/Updated\s+(\d+\s*(?:minutes?|hours?|days?|weeks?|months?|years?)\s+ago)/i);
|
|
185
|
+
const modelCountMatch = text.match(/\bName\s+(\d+)\s+models?\s+Size\b/i) ||
|
|
186
|
+
text.match(/\b(\d+)\s+models?\s+Size\s+Context\s+Input\b/i);
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
pulls: downloadsMatch ? this.parsePulls(downloadsMatch[1].replace(/\s+/g, '')) : 0,
|
|
190
|
+
lastUpdated: updatedMatch ? updatedMatch[1].replace(/\s+/g, ' ').trim() : 'Unknown',
|
|
191
|
+
tagsCount: modelCountMatch ? parseInt(modelCountMatch[1], 10) : 0
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
170
195
|
isCacheValid() {
|
|
171
196
|
const file = fs.existsSync(this.cacheFile) ? this.cacheFile : (fs.existsSync(this.legacyCacheFile) ? this.legacyCacheFile : null);
|
|
172
197
|
if (!file) return false;
|
|
@@ -280,6 +305,8 @@ class OllamaNativeScraper {
|
|
|
280
305
|
...detailedInfo,
|
|
281
306
|
// Usar datos mejorados si están disponibles
|
|
282
307
|
pulls: detailedInfo.actual_pulls || basicModel.pulls || 0,
|
|
308
|
+
last_updated: detailedInfo.last_updated || basicModel.last_updated || 'Unknown',
|
|
309
|
+
tags: detailedInfo.tags_count || detailedInfo.tags?.length || basicModel.tags || 0,
|
|
283
310
|
main_size: detailedInfo.main_size || 'Unknown',
|
|
284
311
|
detailed_scraped_at: new Date().toISOString()
|
|
285
312
|
};
|
|
@@ -302,11 +329,18 @@ class OllamaNativeScraper {
|
|
|
302
329
|
use_cases: [],
|
|
303
330
|
main_size: 'Unknown',
|
|
304
331
|
actual_pulls: 0,
|
|
332
|
+
tags_count: 0,
|
|
333
|
+
last_updated: 'Unknown',
|
|
305
334
|
context_length: 'Unknown',
|
|
306
335
|
input_types: []
|
|
307
336
|
};
|
|
308
337
|
|
|
309
338
|
try {
|
|
339
|
+
const pageSummary = this.extractPageSummary(html);
|
|
340
|
+
details.actual_pulls = pageSummary.pulls;
|
|
341
|
+
details.last_updated = pageSummary.lastUpdated;
|
|
342
|
+
details.tags_count = pageSummary.tagsCount;
|
|
343
|
+
|
|
310
344
|
// MEJORAR: Extraer TODOS los tags incluyendo quantizaciones específicas
|
|
311
345
|
const allTagMatches = [];
|
|
312
346
|
|
|
@@ -370,37 +404,28 @@ class OllamaNativeScraper {
|
|
|
370
404
|
}
|
|
371
405
|
|
|
372
406
|
// NUEVO: Detectar tipos de input soportados
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
-
if (html.toLowerCase().includes('image') || html.toLowerCase().includes('vision') ||
|
|
378
|
-
html.toLowerCase().includes('visual')) {
|
|
407
|
+
const modelNameForInputs = String(basicModel.model_identifier || '').toLowerCase();
|
|
408
|
+
const inputTypes = ['text'];
|
|
409
|
+
if (/llava|bakllava|moondream|vision|vl\b|qwen2\.5vl|qwen-vl|qwen3-vl|minicpm-v|pixtral|gemma3n|llama3\.2-vision/.test(modelNameForInputs)) {
|
|
379
410
|
inputTypes.push('image');
|
|
380
411
|
}
|
|
381
|
-
if (html.toLowerCase().includes('code') || html.toLowerCase().includes('programming')) {
|
|
382
|
-
inputTypes.push('code');
|
|
383
|
-
}
|
|
384
|
-
if (html.toLowerCase().includes('audio') || html.toLowerCase().includes('speech')) {
|
|
385
|
-
inputTypes.push('audio');
|
|
386
|
-
}
|
|
387
412
|
|
|
388
|
-
details.input_types =
|
|
413
|
+
details.input_types = [...new Set(inputTypes)];
|
|
389
414
|
|
|
390
415
|
// Mejor extracción de tamaños con regex más específico
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
416
|
+
details.model_sizes = [...new Set(
|
|
417
|
+
details.tags
|
|
418
|
+
.map(tag => this.extractSizeFromTag(tag))
|
|
419
|
+
.filter(size => size !== 'unknown')
|
|
420
|
+
)];
|
|
421
|
+
if (details.model_sizes.length > 0) {
|
|
422
|
+
details.main_size = details.model_sizes[0];
|
|
398
423
|
}
|
|
399
424
|
|
|
400
425
|
// Extraer pulls reales del HTML
|
|
401
|
-
const pullsMatch = html.match(/(\d+(?:\.\d+)
|
|
426
|
+
const pullsMatch = this.htmlToText(html).match(/(\d+(?:\.\d+)?\s*[KMB]?)\s*(?:pulls?|downloads?)/i);
|
|
402
427
|
if (pullsMatch) {
|
|
403
|
-
details.actual_pulls = this.parsePulls(pullsMatch[1]);
|
|
428
|
+
details.actual_pulls = this.parsePulls(pullsMatch[1].replace(/\s+/g, ''));
|
|
404
429
|
}
|
|
405
430
|
|
|
406
431
|
// Mejorar detección de quantizaciones
|
package/src/ui/cli-theme.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const chalk = require('chalk');
|
|
4
|
+
const { execFileSync } = require('child_process');
|
|
4
5
|
const fs = require('fs');
|
|
5
6
|
const os = require('os');
|
|
6
7
|
const path = require('path');
|
|
@@ -51,14 +52,46 @@ const FRAMES_PER_SECOND = 14;
|
|
|
51
52
|
// Security: do not auto-load executable-style banner sources from user-writable folders.
|
|
52
53
|
// External banner loading is opt-in via LLM_CHECKER_BANNER_SOURCE and supports JSON only.
|
|
53
54
|
const DEFAULT_BANNER_SOURCE = null;
|
|
54
|
-
const
|
|
55
|
-
os.homedir(),
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
const DEFAULT_TEXT_BANNER_SOURCES = [
|
|
56
|
+
path.join(os.homedir(), 'Desktop', 'llm-checker', 'banner-profesional-v2.txt'),
|
|
57
|
+
path.join(
|
|
58
|
+
os.homedir(),
|
|
59
|
+
'Library',
|
|
60
|
+
'Mobile Documents',
|
|
61
|
+
'com~apple~CloudDocs',
|
|
62
|
+
'Desktop',
|
|
63
|
+
'llm-checker',
|
|
64
|
+
'banner-profesional-v2.txt'
|
|
65
|
+
)
|
|
66
|
+
];
|
|
60
67
|
let cachedExternalBanner = null;
|
|
61
68
|
let cachedTextBanner = null;
|
|
69
|
+
let cachedMacOsDarkBackground = null;
|
|
70
|
+
|
|
71
|
+
const TEXT_BANNER_PALETTES = {
|
|
72
|
+
dark: {
|
|
73
|
+
border: '#0066FF',
|
|
74
|
+
primary: '#60A5FA',
|
|
75
|
+
feature: '#A7F3D0',
|
|
76
|
+
subtitle: '#C7D2FE',
|
|
77
|
+
link: '#3B82F6',
|
|
78
|
+
install: '#F8FAFC',
|
|
79
|
+
art: '#F8FAFC',
|
|
80
|
+
solidLogo: ['#F8FAFC', '#E2ECFF', '#DBEAFE', '#E2ECFF'],
|
|
81
|
+
shadedLogo: ['#93C5FD', '#60A5FA', '#38BDF8', '#22D3EE', '#38BDF8', '#60A5FA']
|
|
82
|
+
},
|
|
83
|
+
light: {
|
|
84
|
+
border: '#0047B3',
|
|
85
|
+
primary: '#003A8C',
|
|
86
|
+
feature: '#047857',
|
|
87
|
+
subtitle: '#3730A3',
|
|
88
|
+
link: '#1D4ED8',
|
|
89
|
+
install: '#111827',
|
|
90
|
+
art: '#111827',
|
|
91
|
+
solidLogo: ['#0F172A', '#1E3A8A', '#111827', '#1E40AF'],
|
|
92
|
+
shadedLogo: ['#1D4ED8', '#0369A1', '#0F766E', '#047857', '#1D4ED8']
|
|
93
|
+
}
|
|
94
|
+
};
|
|
62
95
|
|
|
63
96
|
function sleep(ms) {
|
|
64
97
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -68,6 +101,78 @@ function clearTerminal() {
|
|
|
68
101
|
process.stdout.write('\x1b[2J\x1b[0f');
|
|
69
102
|
}
|
|
70
103
|
|
|
104
|
+
function parseDarkBackgroundValue(value) {
|
|
105
|
+
if (typeof value !== 'string' || value.trim() === '') return null;
|
|
106
|
+
const normalized = value.trim().toLowerCase();
|
|
107
|
+
|
|
108
|
+
if (['dark', 'true', '1', 'yes', 'on'].includes(normalized)) return true;
|
|
109
|
+
if (['light', 'false', '0', 'no', 'off'].includes(normalized)) return false;
|
|
110
|
+
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function detectDarkBackgroundFromColorFgbg(value) {
|
|
115
|
+
if (typeof value !== 'string' || value.trim() === '') return null;
|
|
116
|
+
|
|
117
|
+
const parts = value.split(';');
|
|
118
|
+
const backgroundCode = Number.parseInt(parts[parts.length - 1], 10);
|
|
119
|
+
if (!Number.isFinite(backgroundCode)) return null;
|
|
120
|
+
|
|
121
|
+
const lightBackgrounds = new Set([7, 10, 11, 14, 15]);
|
|
122
|
+
const darkBackgrounds = new Set([0, 1, 2, 4, 5, 6, 8]);
|
|
123
|
+
|
|
124
|
+
if (lightBackgrounds.has(backgroundCode)) return false;
|
|
125
|
+
if (darkBackgrounds.has(backgroundCode)) return true;
|
|
126
|
+
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function detectDarkBackgroundFromMacOsAppearance() {
|
|
131
|
+
if (process.platform !== 'darwin') return null;
|
|
132
|
+
if (cachedMacOsDarkBackground !== null) return cachedMacOsDarkBackground;
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const output = execFileSync('defaults', ['read', '-g', 'AppleInterfaceStyle'], {
|
|
136
|
+
encoding: 'utf8',
|
|
137
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
138
|
+
timeout: 100
|
|
139
|
+
});
|
|
140
|
+
cachedMacOsDarkBackground = output.trim().toLowerCase().includes('dark');
|
|
141
|
+
} catch {
|
|
142
|
+
// macOS omits AppleInterfaceStyle when the system appearance is Light.
|
|
143
|
+
cachedMacOsDarkBackground = false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return cachedMacOsDarkBackground;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function resolveHasDarkBackground(options = {}) {
|
|
150
|
+
if (typeof options.hasDarkBackground === 'boolean') {
|
|
151
|
+
return options.hasDarkBackground;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const explicit =
|
|
155
|
+
parseDarkBackgroundValue(process.env.LLM_CHECKER_BANNER_THEME) ??
|
|
156
|
+
parseDarkBackgroundValue(process.env.LLM_CHECKER_HAS_DARK_BACKGROUND) ??
|
|
157
|
+
parseDarkBackgroundValue(process.env.TERM_BACKGROUND);
|
|
158
|
+
|
|
159
|
+
if (explicit !== null) return explicit;
|
|
160
|
+
|
|
161
|
+
const colorFgbg = detectDarkBackgroundFromColorFgbg(process.env.COLORFGBG);
|
|
162
|
+
if (colorFgbg !== null) return colorFgbg;
|
|
163
|
+
|
|
164
|
+
const macOsAppearance = detectDarkBackgroundFromMacOsAppearance();
|
|
165
|
+
if (macOsAppearance !== null) return macOsAppearance;
|
|
166
|
+
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function getTextBannerPalette(options = {}) {
|
|
171
|
+
return resolveHasDarkBackground(options)
|
|
172
|
+
? TEXT_BANNER_PALETTES.dark
|
|
173
|
+
: TEXT_BANNER_PALETTES.light;
|
|
174
|
+
}
|
|
175
|
+
|
|
71
176
|
function fitLine(line, width) {
|
|
72
177
|
const value = String(line || '');
|
|
73
178
|
if (value.length <= width) return value;
|
|
@@ -190,10 +295,12 @@ function loadExternalBanner(sourceFile) {
|
|
|
190
295
|
}
|
|
191
296
|
|
|
192
297
|
function loadTextBanner(sourceFile) {
|
|
193
|
-
const
|
|
298
|
+
const requestedFile =
|
|
194
299
|
sourceFile ||
|
|
195
300
|
process.env.LLM_CHECKER_TEXT_BANNER_SOURCE ||
|
|
196
|
-
|
|
301
|
+
DEFAULT_TEXT_BANNER_SOURCES.find((candidate) => fs.existsSync(candidate)) ||
|
|
302
|
+
DEFAULT_TEXT_BANNER_SOURCES[0];
|
|
303
|
+
const filePath = requestedFile;
|
|
197
304
|
let mtimeMs = -1;
|
|
198
305
|
|
|
199
306
|
try {
|
|
@@ -236,6 +343,7 @@ function loadTextBanner(sourceFile) {
|
|
|
236
343
|
}
|
|
237
344
|
|
|
238
345
|
function drawTextBanner(lines, options = {}) {
|
|
346
|
+
const palette = getTextBannerPalette(options);
|
|
239
347
|
const colorPhase = Number.isFinite(options.colorPhase)
|
|
240
348
|
? Math.max(0, Math.floor(options.colorPhase))
|
|
241
349
|
: 0;
|
|
@@ -265,8 +373,8 @@ function drawTextBanner(lines, options = {}) {
|
|
|
265
373
|
};
|
|
266
374
|
|
|
267
375
|
const colorizeDosRebelLine = (text) => {
|
|
268
|
-
const solidPalette =
|
|
269
|
-
const shadePalette =
|
|
376
|
+
const solidPalette = palette.solidLogo;
|
|
377
|
+
const shadePalette = palette.shadedLogo;
|
|
270
378
|
let out = '';
|
|
271
379
|
for (let index = 0; index < text.length; index += 1) {
|
|
272
380
|
const ch = text[index];
|
|
@@ -292,9 +400,9 @@ function drawTextBanner(lines, options = {}) {
|
|
|
292
400
|
if (/^\s*\+[-+]+\+\s*$/.test(line)) {
|
|
293
401
|
if (terminalWidth && terminalWidth >= 10) {
|
|
294
402
|
const inner = Math.max(6, terminalWidth - 4);
|
|
295
|
-
console.log(chalk.hex(
|
|
403
|
+
console.log(chalk.hex(palette.border)(` +${'-'.repeat(inner)}+ `));
|
|
296
404
|
} else {
|
|
297
|
-
console.log(chalk.hex(
|
|
405
|
+
console.log(chalk.hex(palette.border)(line));
|
|
298
406
|
}
|
|
299
407
|
continue;
|
|
300
408
|
}
|
|
@@ -305,8 +413,8 @@ function drawTextBanner(lines, options = {}) {
|
|
|
305
413
|
continue;
|
|
306
414
|
}
|
|
307
415
|
|
|
308
|
-
const left = chalk.hex(
|
|
309
|
-
const right = chalk.hex(
|
|
416
|
+
const left = chalk.hex(palette.border)(frameMatch[1]);
|
|
417
|
+
const right = chalk.hex(palette.border)(frameMatch[3]);
|
|
310
418
|
const content = frameMatch[2];
|
|
311
419
|
const maxInnerWidth = terminalWidth
|
|
312
420
|
? Math.max(0, terminalWidth - (frameMatch[1].length + frameMatch[3].length))
|
|
@@ -336,7 +444,7 @@ function drawTextBanner(lines, options = {}) {
|
|
|
336
444
|
fittedContent.includes('Deterministic scoring across') ||
|
|
337
445
|
fittedContent.includes('Run: llm-checker recommend')
|
|
338
446
|
) {
|
|
339
|
-
inner = chalk.hex(
|
|
447
|
+
inner = chalk.hex(palette.primary)(fittedContent);
|
|
340
448
|
} else if (
|
|
341
449
|
fittedContent.includes('[200+ DYNAMIC MODELS]') ||
|
|
342
450
|
fittedContent.includes('[35+ FALLBACK]') ||
|
|
@@ -344,18 +452,18 @@ function drawTextBanner(lines, options = {}) {
|
|
|
344
452
|
fittedContent.includes('[MULTI-GPU]') ||
|
|
345
453
|
fittedContent.includes('[MCP SERVER]')
|
|
346
454
|
) {
|
|
347
|
-
inner = chalk.hex(
|
|
455
|
+
inner = chalk.hex(palette.feature)(fittedContent);
|
|
348
456
|
} else if (
|
|
349
457
|
fittedContent.includes('AI-powered CLI for hardware-aware local LLM recommendations')
|
|
350
458
|
) {
|
|
351
|
-
inner = chalk.hex(
|
|
459
|
+
inner = chalk.hex(palette.subtitle)(fittedContent);
|
|
352
460
|
} else if (
|
|
353
461
|
fittedContent.includes('github.com/Pavelevich/llm-checker') ||
|
|
354
462
|
fittedContent.includes('npmjs.com/package/llm-checker')
|
|
355
463
|
) {
|
|
356
|
-
inner = chalk.hex(
|
|
464
|
+
inner = chalk.hex(palette.link)(fittedContent);
|
|
357
465
|
} else if (fittedContent.includes('Install: npm install -g llm-checker')) {
|
|
358
|
-
inner = chalk.hex(
|
|
466
|
+
inner = chalk.hex(palette.install)(fittedContent);
|
|
359
467
|
} else if (
|
|
360
468
|
fittedContent.includes('█') ||
|
|
361
469
|
fittedContent.includes('░') ||
|
|
@@ -369,7 +477,7 @@ function drawTextBanner(lines, options = {}) {
|
|
|
369
477
|
fittedContent.includes('▀') ||
|
|
370
478
|
fittedContent.includes('▄')
|
|
371
479
|
) {
|
|
372
|
-
inner = chalk.hex(
|
|
480
|
+
inner = chalk.hex(palette.art)(fittedContent);
|
|
373
481
|
}
|
|
374
482
|
|
|
375
483
|
console.log(left + inner + right);
|
|
@@ -625,14 +733,14 @@ async function animateBanner(options = {}) {
|
|
|
625
733
|
if (textBanner && textBanner.length > 0) {
|
|
626
734
|
if (!shouldAnimate) {
|
|
627
735
|
clearTerminal();
|
|
628
|
-
drawTextBanner(textBanner);
|
|
736
|
+
drawTextBanner(textBanner, { hasDarkBackground });
|
|
629
737
|
return;
|
|
630
738
|
}
|
|
631
739
|
|
|
632
740
|
const textFrames = Math.max(10, Math.min(24, frames));
|
|
633
741
|
for (let frameIndex = 0; frameIndex < textFrames; frameIndex += 1) {
|
|
634
742
|
clearTerminal();
|
|
635
|
-
drawTextBanner(textBanner, { colorPhase: frameIndex });
|
|
743
|
+
drawTextBanner(textBanner, { colorPhase: frameIndex, hasDarkBackground });
|
|
636
744
|
await sleep(frameDurationMs);
|
|
637
745
|
}
|
|
638
746
|
return;
|
|
@@ -658,7 +766,11 @@ function renderPersistentBanner(width = 74, options = {}) {
|
|
|
658
766
|
return;
|
|
659
767
|
}
|
|
660
768
|
|
|
661
|
-
const prepared = makeFrames({
|
|
769
|
+
const prepared = makeFrames({
|
|
770
|
+
frameCount: 1,
|
|
771
|
+
width,
|
|
772
|
+
hasDarkBackground: resolveHasDarkBackground(options)
|
|
773
|
+
});
|
|
662
774
|
drawFrame(prepared.frames[0], prepared.width, prepared.theme);
|
|
663
775
|
}
|
|
664
776
|
|
|
@@ -674,7 +786,10 @@ module.exports = {
|
|
|
674
786
|
renderPersistentBanner,
|
|
675
787
|
renderCommandHeader,
|
|
676
788
|
__private: {
|
|
789
|
+
detectDarkBackgroundFromColorFgbg,
|
|
790
|
+
detectDarkBackgroundFromMacOsAppearance,
|
|
677
791
|
makeFrames,
|
|
678
|
-
drawFrame
|
|
792
|
+
drawFrame,
|
|
793
|
+
resolveHasDarkBackground
|
|
679
794
|
}
|
|
680
795
|
};
|
|
@@ -13,6 +13,7 @@ const PRIMARY_COMMAND_PRIORITY = [
|
|
|
13
13
|
'recommend',
|
|
14
14
|
'simulate',
|
|
15
15
|
'ai-run',
|
|
16
|
+
'sync',
|
|
16
17
|
'ollama-plan',
|
|
17
18
|
'list-models',
|
|
18
19
|
'search',
|
|
@@ -314,7 +315,6 @@ async function collectCommandArgs(commandMeta) {
|
|
|
314
315
|
const prompts = [];
|
|
315
316
|
const requiredPrompts = REQUIRED_ARG_PROMPTS[commandMeta.name] || [];
|
|
316
317
|
const requiredOptionPrompts = getRequiredOptionPrompts(commandMeta);
|
|
317
|
-
const promptOptionalArgs = commandMeta.name !== 'help';
|
|
318
318
|
|
|
319
319
|
for (const requiredPrompt of requiredPrompts) {
|
|
320
320
|
prompts.push({
|
|
@@ -354,16 +354,6 @@ async function collectCommandArgs(commandMeta) {
|
|
|
354
354
|
});
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
-
if (promptOptionalArgs) {
|
|
358
|
-
prompts.push({
|
|
359
|
-
type: 'input',
|
|
360
|
-
name: 'extraArgs',
|
|
361
|
-
message: 'Optional extra params (example: --json --limit 5):',
|
|
362
|
-
prefix: ' ',
|
|
363
|
-
default: ''
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
|
|
367
357
|
if (prompts.length === 0) {
|
|
368
358
|
return [];
|
|
369
359
|
}
|
|
@@ -378,13 +368,6 @@ async function collectCommandArgs(commandMeta) {
|
|
|
378
368
|
|
|
379
369
|
args.push(...buildRequiredOptionArgs(requiredOptionPrompts, answers));
|
|
380
370
|
|
|
381
|
-
if (promptOptionalArgs) {
|
|
382
|
-
const extraArgs = String(answers.extraArgs || '').trim();
|
|
383
|
-
if (extraArgs) {
|
|
384
|
-
args.push(...tokenizeArgString(extraArgs));
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
371
|
return args;
|
|
389
372
|
}
|
|
390
373
|
|