llm-checker 3.4.2 → 3.5.1
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 +32 -10
- package/analyzer/performance.js +40 -94
- package/bin/enhanced_cli.js +320 -254
- package/bin/mcp-server.mjs +0 -0
- package/package.json +1 -1
- package/src/models/ai-check-selector.js +2 -2
- package/src/models/deterministic-selector.js +1 -0
- package/src/models/expanded_database.js +10 -83
- package/src/ollama/client.js +29 -4
- package/src/ui/cli-theme.js +733 -0
- package/src/ui/interactive-panel.js +599 -0
- package/src/utils/fetch.js +17 -0
- package/src/utils/token-speed-estimator.js +207 -0
- package/src/ollama/gpu-placement-planner.js +0 -496
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const inquirer = require('inquirer');
|
|
5
|
+
const readline = require('readline');
|
|
6
|
+
const { spawn } = require('child_process');
|
|
7
|
+
const { animateBanner, renderPersistentBanner } = require('./cli-theme');
|
|
8
|
+
|
|
9
|
+
const PRIMARY_COMMAND_PRIORITY = [
|
|
10
|
+
'check',
|
|
11
|
+
'help',
|
|
12
|
+
'mcp-setup',
|
|
13
|
+
'recommend',
|
|
14
|
+
'ai-run',
|
|
15
|
+
'ollama-plan',
|
|
16
|
+
'list-models',
|
|
17
|
+
'search',
|
|
18
|
+
'installed',
|
|
19
|
+
'ollama'
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const REQUIRED_ARG_PROMPTS = {
|
|
23
|
+
search: [
|
|
24
|
+
{
|
|
25
|
+
name: 'query',
|
|
26
|
+
message: 'Search text for `search`:',
|
|
27
|
+
validate: (value) => (value && value.trim() ? true : 'Type a search query')
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function clearTerminal() {
|
|
33
|
+
process.stdout.write('\x1b[2J\x1b[0f');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function truncateText(text, maxLength) {
|
|
37
|
+
const value = String(text || '');
|
|
38
|
+
if (value.length <= maxLength) return value;
|
|
39
|
+
if (maxLength <= 3) return value.slice(0, maxLength);
|
|
40
|
+
return `${value.slice(0, maxLength - 3)}...`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function tokenizeArgString(rawInput = '') {
|
|
44
|
+
const tokens = [];
|
|
45
|
+
let current = '';
|
|
46
|
+
let quote = null;
|
|
47
|
+
let escape = false;
|
|
48
|
+
|
|
49
|
+
for (const char of String(rawInput)) {
|
|
50
|
+
if (escape) {
|
|
51
|
+
current += char;
|
|
52
|
+
escape = false;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (char === '\\') {
|
|
57
|
+
escape = true;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (quote) {
|
|
62
|
+
if (char === quote) {
|
|
63
|
+
quote = null;
|
|
64
|
+
} else {
|
|
65
|
+
current += char;
|
|
66
|
+
}
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (char === '"' || char === '\'') {
|
|
71
|
+
quote = char;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (/\s/.test(char)) {
|
|
76
|
+
if (current.length > 0) {
|
|
77
|
+
tokens.push(current);
|
|
78
|
+
current = '';
|
|
79
|
+
}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
current += char;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (current.length > 0) {
|
|
87
|
+
tokens.push(current);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return tokens;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getRequiredOptionPrompts(commandMeta) {
|
|
94
|
+
const commandOptions = Array.isArray(commandMeta?.command?.options)
|
|
95
|
+
? commandMeta.command.options
|
|
96
|
+
: [];
|
|
97
|
+
|
|
98
|
+
const isMandatoryOption = (option) => {
|
|
99
|
+
if (!option) return false;
|
|
100
|
+
if (typeof option.mandatory === 'boolean') {
|
|
101
|
+
return option.mandatory;
|
|
102
|
+
}
|
|
103
|
+
return Boolean(option.required);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return commandOptions
|
|
107
|
+
.filter((option) => isMandatoryOption(option))
|
|
108
|
+
.map((option) => {
|
|
109
|
+
const flag = option.long || option.short || option.flags.split(/[,\s|]+/).find((token) => token.startsWith('-'));
|
|
110
|
+
const attributeName =
|
|
111
|
+
typeof option.attributeName === 'function'
|
|
112
|
+
? option.attributeName()
|
|
113
|
+
: String(flag || 'required').replace(/^-+/, '').replace(/[^a-zA-Z0-9]+/g, '_');
|
|
114
|
+
const optionType = typeof option.isBoolean === 'function' && option.isBoolean() ? 'boolean' : 'value';
|
|
115
|
+
const promptName = `required_${attributeName}`;
|
|
116
|
+
const optionFlags = option.flags || flag || attributeName;
|
|
117
|
+
const message =
|
|
118
|
+
optionType === 'boolean'
|
|
119
|
+
? `Required flag ${optionFlags} (enable?):`
|
|
120
|
+
: `Required option ${optionFlags}:`;
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
promptName,
|
|
124
|
+
flag,
|
|
125
|
+
optionFlags,
|
|
126
|
+
variadic: Boolean(option.variadic),
|
|
127
|
+
optionType,
|
|
128
|
+
message
|
|
129
|
+
};
|
|
130
|
+
})
|
|
131
|
+
.filter((entry) => Boolean(entry.flag));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function normalizeVariadicValue(rawValue) {
|
|
135
|
+
const tokens = tokenizeArgString(rawValue);
|
|
136
|
+
const values = [];
|
|
137
|
+
|
|
138
|
+
tokens.forEach((token) => {
|
|
139
|
+
String(token)
|
|
140
|
+
.split(',')
|
|
141
|
+
.map((piece) => piece.trim())
|
|
142
|
+
.filter(Boolean)
|
|
143
|
+
.forEach((piece) => values.push(piece));
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return values;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function buildRequiredOptionArgs(requiredOptionPrompts, answers) {
|
|
150
|
+
const args = [];
|
|
151
|
+
|
|
152
|
+
requiredOptionPrompts.forEach((entry) => {
|
|
153
|
+
const answer = answers ? answers[entry.promptName] : undefined;
|
|
154
|
+
|
|
155
|
+
if (entry.optionType === 'boolean') {
|
|
156
|
+
if (answer) args.push(entry.flag);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const rawValue = String(answer || '').trim();
|
|
161
|
+
if (!rawValue) return;
|
|
162
|
+
|
|
163
|
+
if (entry.variadic) {
|
|
164
|
+
const values = normalizeVariadicValue(rawValue);
|
|
165
|
+
if (values.length > 0) {
|
|
166
|
+
args.push(entry.flag, ...values);
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
args.push(entry.flag, rawValue);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return args;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function buildCommandCatalog(program) {
|
|
178
|
+
return (program.commands || [])
|
|
179
|
+
.map((command) => ({
|
|
180
|
+
name: command.name(),
|
|
181
|
+
description: command.description() || 'No description available',
|
|
182
|
+
command
|
|
183
|
+
}))
|
|
184
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function buildPrimaryCommands(catalog) {
|
|
188
|
+
const byName = new Map(catalog.map((item) => [item.name, item]));
|
|
189
|
+
const ordered = [];
|
|
190
|
+
|
|
191
|
+
for (const name of PRIMARY_COMMAND_PRIORITY) {
|
|
192
|
+
if (byName.has(name)) ordered.push(byName.get(name));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (ordered.length === 0) {
|
|
196
|
+
return catalog.slice(0, 8);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return ordered;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function getVisibleCommands(state, catalog, primaryCommands) {
|
|
203
|
+
if (!state.paletteOpen) {
|
|
204
|
+
return primaryCommands;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const query = state.query.trim().toLowerCase();
|
|
208
|
+
if (!query) {
|
|
209
|
+
return catalog;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return catalog.filter((item) => {
|
|
213
|
+
return (
|
|
214
|
+
item.name.toLowerCase().includes(query) ||
|
|
215
|
+
item.description.toLowerCase().includes(query)
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function getCommandWindow(commands, selectedIndex, maxRows) {
|
|
221
|
+
const total = commands.length;
|
|
222
|
+
if (total <= maxRows) {
|
|
223
|
+
return {
|
|
224
|
+
start: 0,
|
|
225
|
+
end: total,
|
|
226
|
+
items: commands
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const half = Math.floor(maxRows / 2);
|
|
231
|
+
let start = Math.max(0, selectedIndex - half);
|
|
232
|
+
if (start + maxRows > total) {
|
|
233
|
+
start = total - maxRows;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const end = Math.min(total, start + maxRows);
|
|
237
|
+
return {
|
|
238
|
+
start,
|
|
239
|
+
end,
|
|
240
|
+
items: commands.slice(start, end)
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function renderPanel(state, catalog, primaryCommands, options = {}) {
|
|
245
|
+
const colorPhase = Number.isFinite(options.colorPhase) ? options.colorPhase : 0;
|
|
246
|
+
const width = Math.max(76, Math.min(process.stdout.columns || 100, 128));
|
|
247
|
+
const separator = '-'.repeat(width - 2);
|
|
248
|
+
const visibleCommands = getVisibleCommands(state, catalog, primaryCommands);
|
|
249
|
+
const maxCommandRows = Math.max(6, Math.min(16, (process.stdout.rows || 40) - 28));
|
|
250
|
+
const selectedIndex =
|
|
251
|
+
visibleCommands.length > 0
|
|
252
|
+
? Math.max(0, Math.min(state.selected, visibleCommands.length - 1))
|
|
253
|
+
: 0;
|
|
254
|
+
if (selectedIndex !== state.selected) {
|
|
255
|
+
state.selected = selectedIndex;
|
|
256
|
+
}
|
|
257
|
+
const commandWindow = getCommandWindow(visibleCommands, selectedIndex, maxCommandRows);
|
|
258
|
+
|
|
259
|
+
clearTerminal();
|
|
260
|
+
renderPersistentBanner(undefined, { colorPhase });
|
|
261
|
+
console.log('');
|
|
262
|
+
console.log(chalk.gray(separator));
|
|
263
|
+
const inputLabel = state.paletteOpen ? `/${state.query}` : '';
|
|
264
|
+
console.log(`${chalk.white.bold('>')} ${chalk.white(inputLabel)}${chalk.gray('|')}`);
|
|
265
|
+
console.log(chalk.gray(separator));
|
|
266
|
+
|
|
267
|
+
const title = state.paletteOpen
|
|
268
|
+
? 'All commands (filtered with `/`)'
|
|
269
|
+
: 'Main commands';
|
|
270
|
+
console.log(chalk.cyan(title));
|
|
271
|
+
console.log('');
|
|
272
|
+
|
|
273
|
+
if (visibleCommands.length === 0) {
|
|
274
|
+
console.log(chalk.yellow('No commands match your query.'));
|
|
275
|
+
} else {
|
|
276
|
+
const commandCol = 26;
|
|
277
|
+
const descCol = Math.max(18, width - commandCol - 8);
|
|
278
|
+
const hiddenAbove = commandWindow.start;
|
|
279
|
+
const hiddenBelow = visibleCommands.length - commandWindow.end;
|
|
280
|
+
|
|
281
|
+
if (hiddenAbove > 0) {
|
|
282
|
+
console.log(chalk.gray(`... ${hiddenAbove} command(s) above`));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
commandWindow.items.forEach((item, index) => {
|
|
286
|
+
const globalIndex = commandWindow.start + index;
|
|
287
|
+
const active = globalIndex === selectedIndex;
|
|
288
|
+
const commandText = truncateText(`/${item.name}`, commandCol).padEnd(commandCol, ' ');
|
|
289
|
+
const description = truncateText(item.description, descCol);
|
|
290
|
+
|
|
291
|
+
if (active) {
|
|
292
|
+
console.log(
|
|
293
|
+
`${chalk.hex('#7c5cff').bold(commandText)} ${chalk.white(description)}`
|
|
294
|
+
);
|
|
295
|
+
} else {
|
|
296
|
+
console.log(`${chalk.gray(commandText)} ${chalk.gray(description)}`);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
if (hiddenBelow > 0) {
|
|
301
|
+
console.log(chalk.gray(`... ${hiddenBelow} command(s) below`));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
console.log('');
|
|
306
|
+
console.log(chalk.gray(separator));
|
|
307
|
+
console.log(
|
|
308
|
+
chalk.gray('up/down navigate | Enter run | / open all | Esc close all | q exit')
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function collectCommandArgs(commandMeta) {
|
|
313
|
+
const prompts = [];
|
|
314
|
+
const requiredPrompts = REQUIRED_ARG_PROMPTS[commandMeta.name] || [];
|
|
315
|
+
const requiredOptionPrompts = getRequiredOptionPrompts(commandMeta);
|
|
316
|
+
const promptOptionalArgs = commandMeta.name !== 'help';
|
|
317
|
+
|
|
318
|
+
for (const requiredPrompt of requiredPrompts) {
|
|
319
|
+
prompts.push({
|
|
320
|
+
type: 'input',
|
|
321
|
+
name: requiredPrompt.name,
|
|
322
|
+
message: requiredPrompt.message,
|
|
323
|
+
prefix: ' ',
|
|
324
|
+
validate: requiredPrompt.validate
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
for (const requiredOptionPrompt of requiredOptionPrompts) {
|
|
329
|
+
prompts.push({
|
|
330
|
+
type: requiredOptionPrompt.optionType === 'boolean' ? 'confirm' : 'input',
|
|
331
|
+
name: requiredOptionPrompt.promptName,
|
|
332
|
+
message: requiredOptionPrompt.message,
|
|
333
|
+
prefix: ' ',
|
|
334
|
+
default: requiredOptionPrompt.optionType === 'boolean' ? true : '',
|
|
335
|
+
validate:
|
|
336
|
+
requiredOptionPrompt.optionType === 'boolean'
|
|
337
|
+
? undefined
|
|
338
|
+
: (value) => {
|
|
339
|
+
const trimmed = String(value || '').trim();
|
|
340
|
+
if (!trimmed) {
|
|
341
|
+
return `Provide a value for ${requiredOptionPrompt.optionFlags}`;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (requiredOptionPrompt.variadic) {
|
|
345
|
+
const values = normalizeVariadicValue(trimmed);
|
|
346
|
+
if (values.length === 0) {
|
|
347
|
+
return `Provide at least one value for ${requiredOptionPrompt.optionFlags}`;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (promptOptionalArgs) {
|
|
357
|
+
prompts.push({
|
|
358
|
+
type: 'input',
|
|
359
|
+
name: 'extraArgs',
|
|
360
|
+
message: 'Optional extra params (example: --json --limit 5):',
|
|
361
|
+
prefix: ' ',
|
|
362
|
+
default: ''
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (prompts.length === 0) {
|
|
367
|
+
return [];
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const answers = await inquirer.prompt(prompts);
|
|
371
|
+
const args = [];
|
|
372
|
+
|
|
373
|
+
for (const requiredPrompt of requiredPrompts) {
|
|
374
|
+
const value = String(answers[requiredPrompt.name] || '').trim();
|
|
375
|
+
if (value) args.push(value);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
args.push(...buildRequiredOptionArgs(requiredOptionPrompts, answers));
|
|
379
|
+
|
|
380
|
+
if (promptOptionalArgs) {
|
|
381
|
+
const extraArgs = String(answers.extraArgs || '').trim();
|
|
382
|
+
if (extraArgs) {
|
|
383
|
+
args.push(...tokenizeArgString(extraArgs));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return args;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async function runSelectedCommand(binaryPath, commandMeta) {
|
|
391
|
+
const args = await collectCommandArgs(commandMeta);
|
|
392
|
+
const childArgs = [binaryPath, commandMeta.name, ...args];
|
|
393
|
+
|
|
394
|
+
await new Promise((resolve, reject) => {
|
|
395
|
+
const child = spawn(process.execPath, childArgs, {
|
|
396
|
+
stdio: 'inherit',
|
|
397
|
+
env: process.env
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
child.on('error', reject);
|
|
401
|
+
child.on('close', () => resolve());
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async function launchInteractivePanel(options) {
|
|
406
|
+
const {
|
|
407
|
+
program,
|
|
408
|
+
binaryPath,
|
|
409
|
+
appName = 'llm-checker'
|
|
410
|
+
} = options;
|
|
411
|
+
|
|
412
|
+
if (!program || !binaryPath) {
|
|
413
|
+
throw new Error('launchInteractivePanel requires { program, binaryPath }');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const catalog = buildCommandCatalog(program);
|
|
417
|
+
if (catalog.length === 0) {
|
|
418
|
+
throw new Error('No commands available for interactive panel');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const primaryCommands = buildPrimaryCommands(catalog);
|
|
422
|
+
const state = {
|
|
423
|
+
paletteOpen: false,
|
|
424
|
+
query: '',
|
|
425
|
+
selected: 0,
|
|
426
|
+
busy: false,
|
|
427
|
+
colorPhase: 0
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const renderNow = () => {
|
|
431
|
+
renderPanel(state, catalog, primaryCommands, { colorPhase: state.colorPhase });
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
await animateBanner({ text: appName });
|
|
435
|
+
renderNow();
|
|
436
|
+
|
|
437
|
+
readline.emitKeypressEvents(process.stdin);
|
|
438
|
+
|
|
439
|
+
return new Promise((resolve) => {
|
|
440
|
+
const shouldPulseBanner =
|
|
441
|
+
process.stdout.isTTY &&
|
|
442
|
+
process.env.LLM_CHECKER_DISABLE_ANIMATION !== '1';
|
|
443
|
+
let pulseTimer = null;
|
|
444
|
+
|
|
445
|
+
const stopBannerPulse = () => {
|
|
446
|
+
if (pulseTimer) {
|
|
447
|
+
clearInterval(pulseTimer);
|
|
448
|
+
pulseTimer = null;
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
const startBannerPulse = () => {
|
|
453
|
+
if (!shouldPulseBanner || pulseTimer) return;
|
|
454
|
+
pulseTimer = setInterval(() => {
|
|
455
|
+
if (state.busy) return;
|
|
456
|
+
state.colorPhase = (state.colorPhase + 1) % 1024;
|
|
457
|
+
renderNow();
|
|
458
|
+
}, 120);
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const stopInteractiveMode = () => {
|
|
462
|
+
stopBannerPulse();
|
|
463
|
+
process.stdin.off('keypress', onKeypress);
|
|
464
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
465
|
+
process.stdin.pause();
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
const startInteractiveMode = () => {
|
|
469
|
+
process.stdin.on('keypress', onKeypress);
|
|
470
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
471
|
+
process.stdin.resume();
|
|
472
|
+
startBannerPulse();
|
|
473
|
+
renderNow();
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
const exitPanel = () => {
|
|
477
|
+
stopInteractiveMode();
|
|
478
|
+
process.stdout.write('\n');
|
|
479
|
+
resolve();
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
const withCommandExecution = async (commandMeta) => {
|
|
483
|
+
state.busy = true;
|
|
484
|
+
stopInteractiveMode();
|
|
485
|
+
|
|
486
|
+
try {
|
|
487
|
+
await runSelectedCommand(binaryPath, commandMeta);
|
|
488
|
+
await inquirer.prompt([
|
|
489
|
+
{
|
|
490
|
+
type: 'input',
|
|
491
|
+
name: 'continue',
|
|
492
|
+
message: 'Press Enter to return to the panel',
|
|
493
|
+
prefix: ' '
|
|
494
|
+
}
|
|
495
|
+
]);
|
|
496
|
+
} catch (error) {
|
|
497
|
+
await inquirer.prompt([
|
|
498
|
+
{
|
|
499
|
+
type: 'input',
|
|
500
|
+
name: 'continue',
|
|
501
|
+
message: `Command failed (${error.message}). Press Enter to continue`,
|
|
502
|
+
prefix: ' '
|
|
503
|
+
}
|
|
504
|
+
]);
|
|
505
|
+
} finally {
|
|
506
|
+
state.busy = false;
|
|
507
|
+
startInteractiveMode();
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
const onKeypress = (str, key = {}) => {
|
|
512
|
+
if (state.busy) return;
|
|
513
|
+
|
|
514
|
+
if (key.ctrl && key.name === 'c') {
|
|
515
|
+
exitPanel();
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (str === 'q' && !state.paletteOpen) {
|
|
520
|
+
exitPanel();
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const visibleCommands = getVisibleCommands(state, catalog, primaryCommands);
|
|
525
|
+
if (visibleCommands.length === 0 && key.name === 'return') {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (key.name === 'up' || key.name === 'down') {
|
|
530
|
+
if (visibleCommands.length > 0) {
|
|
531
|
+
const direction = key.name === 'up' ? -1 : 1;
|
|
532
|
+
state.selected =
|
|
533
|
+
(state.selected + direction + visibleCommands.length) % visibleCommands.length;
|
|
534
|
+
renderNow();
|
|
535
|
+
}
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (key.name === 'return') {
|
|
540
|
+
const selectedCommand = visibleCommands[state.selected];
|
|
541
|
+
if (selectedCommand) {
|
|
542
|
+
void withCommandExecution(selectedCommand);
|
|
543
|
+
}
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (!state.paletteOpen && str === '/') {
|
|
548
|
+
state.paletteOpen = true;
|
|
549
|
+
state.query = '';
|
|
550
|
+
state.selected = 0;
|
|
551
|
+
renderNow();
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (state.paletteOpen && key.name === 'escape') {
|
|
556
|
+
state.paletteOpen = false;
|
|
557
|
+
state.query = '';
|
|
558
|
+
state.selected = 0;
|
|
559
|
+
renderNow();
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (!state.paletteOpen) {
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (key.name === 'backspace') {
|
|
568
|
+
if (state.query.length > 0) {
|
|
569
|
+
state.query = state.query.slice(0, -1);
|
|
570
|
+
state.selected = 0;
|
|
571
|
+
renderNow();
|
|
572
|
+
}
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (str && !key.ctrl && !key.meta && /^[a-zA-Z0-9._:-]$/.test(str)) {
|
|
577
|
+
state.query += str;
|
|
578
|
+
state.selected = 0;
|
|
579
|
+
renderNow();
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
startInteractiveMode();
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
module.exports = {
|
|
588
|
+
launchInteractivePanel,
|
|
589
|
+
__private: {
|
|
590
|
+
tokenizeArgString,
|
|
591
|
+
getRequiredOptionPrompts,
|
|
592
|
+
buildRequiredOptionArgs,
|
|
593
|
+
normalizeVariadicValue,
|
|
594
|
+
buildCommandCatalog,
|
|
595
|
+
buildPrimaryCommands,
|
|
596
|
+
getVisibleCommands,
|
|
597
|
+
truncateText
|
|
598
|
+
}
|
|
599
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
let nodeFetchPromise = null;
|
|
2
|
+
|
|
3
|
+
function getNodeFetch() {
|
|
4
|
+
if (!nodeFetchPromise) {
|
|
5
|
+
nodeFetchPromise = import('node-fetch').then((mod) => mod.default || mod);
|
|
6
|
+
}
|
|
7
|
+
return nodeFetchPromise;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function fetchWithFallback(...args) {
|
|
11
|
+
if (typeof globalThis.fetch === 'function') {
|
|
12
|
+
return globalThis.fetch(...args);
|
|
13
|
+
}
|
|
14
|
+
return getNodeFetch().then((fetchImpl) => fetchImpl(...args));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = fetchWithFallback;
|