vibecodingmachine-cli 2026.2.26-1739 ā 2026.3.9-1621
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/bin/auth/auth-compliance.js +7 -1
- package/bin/commands/agent-commands.js +150 -228
- package/bin/commands/command-aliases.js +68 -0
- package/bin/vibecodingmachine.js +1 -2
- package/package.json +2 -2
- package/src/commands/agents/list.js +71 -115
- package/src/commands/agents-check.js +16 -4
- package/src/commands/analyze-file-sizes.js +1 -1
- package/src/commands/auto-direct/auto-provider-manager.js +290 -0
- package/src/commands/auto-direct/auto-status-display.js +331 -0
- package/src/commands/auto-direct/auto-utils.js +439 -0
- package/src/commands/auto-direct/file-operations.js +110 -0
- package/src/commands/auto-direct/provider-config.js +1 -1
- package/src/commands/auto-direct/provider-manager.js +1 -1
- package/src/commands/auto-direct/status-display.js +1 -1
- package/src/commands/auto-direct/utils.js +24 -18
- package/src/commands/auto-direct-refactored.js +413 -0
- package/src/commands/auto-direct.js +594 -188
- package/src/commands/requirements/commands.js +353 -0
- package/src/commands/requirements/default-handlers.js +272 -0
- package/src/commands/requirements/disable.js +97 -0
- package/src/commands/requirements/enable.js +97 -0
- package/src/commands/requirements/utils.js +194 -0
- package/src/commands/requirements-refactored.js +60 -0
- package/src/commands/requirements.js +38 -771
- package/src/commands/specs/disable.js +96 -0
- package/src/commands/specs/enable.js +96 -0
- package/src/trui/TruiInterface.js +5 -11
- package/src/trui/agents/AgentInterface.js +24 -396
- package/src/trui/agents/handlers/CommandHandler.js +93 -0
- package/src/trui/agents/handlers/ContextManager.js +117 -0
- package/src/trui/agents/handlers/DisplayHandler.js +243 -0
- package/src/trui/agents/handlers/HelpHandler.js +51 -0
- package/src/utils/auth.js +13 -111
- package/src/utils/config.js +5 -1
- package/src/utils/interactive/requirements-navigation.js +17 -15
- package/src/utils/interactive-broken.js +2 -2
- package/src/utils/provider-checker/agent-runner.js +15 -1
- package/src/utils/provider-checker/cli-installer.js +149 -7
- package/src/utils/provider-checker/opencode-checker.js +588 -0
- package/src/utils/provider-checker/provider-validator.js +88 -3
- package/src/utils/provider-checker/time-formatter.js +3 -2
- package/src/utils/provider-manager.js +28 -20
- package/src/utils/provider-registry.js +35 -3
- package/src/utils/requirements-navigator/index.js +94 -0
- package/src/utils/requirements-navigator/input-handler.js +217 -0
- package/src/utils/requirements-navigator/section-loader.js +188 -0
- package/src/utils/requirements-navigator/tree-builder.js +105 -0
- package/src/utils/requirements-navigator/tree-renderer.js +50 -0
- package/src/utils/requirements-navigator.js +2 -583
- package/src/utils/trui-clarifications.js +188 -0
- package/src/utils/trui-feedback.js +54 -1
- package/src/utils/trui-kiro-integration.js +398 -0
- package/src/utils/trui-main-handlers.js +194 -0
- package/src/utils/trui-main-menu.js +235 -0
- package/src/utils/trui-nav-agents.js +178 -25
- package/src/utils/trui-nav-requirements.js +203 -27
- package/src/utils/trui-nav-settings.js +114 -1
- package/src/utils/trui-nav-specifications.js +44 -3
- package/src/utils/trui-navigation-backup.js +603 -0
- package/src/utils/trui-navigation.js +70 -228
- package/src/utils/trui-provider-health.js +274 -0
- package/src/utils/trui-provider-manager.js +376 -0
- package/src/utils/trui-quick-menu.js +25 -1
- package/src/utils/trui-req-actions-backup.js +507 -0
- package/src/utils/trui-req-actions.js +148 -216
- package/src/utils/trui-req-editor.js +170 -0
- package/src/utils/trui-req-file-ops.js +278 -0
- package/src/utils/trui-req-tree-old.js +719 -0
- package/src/utils/trui-req-tree.js +348 -627
- package/src/utils/trui-specifications.js +25 -7
- package/src/utils/trui-windsurf.js +231 -10
- package/src/utils/welcome-screen-extracted.js +2 -2
- package/src/utils/welcome-screen.js +2 -2
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TRUI Provider Manager
|
|
3
|
+
*
|
|
4
|
+
* Advanced provider management with health metrics, quota checking, IDE detection
|
|
5
|
+
* Enable/disable providers and re-order them.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
const { showQuickMenu } = require('./trui-quick-menu');
|
|
10
|
+
const { debugLogger } = require('./trui-debug');
|
|
11
|
+
const { getProviderHealth, formatHealthDisplay } = require('./trui-provider-health');
|
|
12
|
+
const { getKiroIntegrationStatus } = require('./trui-kiro-integration');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get provider status icon
|
|
16
|
+
*/
|
|
17
|
+
function getStatusIcon(enabled) {
|
|
18
|
+
return enabled ? 'š¢' : 'š“';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Load providers data via RUI resolver with health checks
|
|
23
|
+
*/
|
|
24
|
+
async function loadProvidersData(navigation) {
|
|
25
|
+
try {
|
|
26
|
+
const result = navigation.resolver.resolve('list providers');
|
|
27
|
+
if (!result.success) return null;
|
|
28
|
+
|
|
29
|
+
const commandResult = await result.command.execute();
|
|
30
|
+
if (!commandResult.success || !commandResult.data) return null;
|
|
31
|
+
|
|
32
|
+
const providers = commandResult.data;
|
|
33
|
+
|
|
34
|
+
// Enhance providers with health information
|
|
35
|
+
for (const provider of providers) {
|
|
36
|
+
try {
|
|
37
|
+
provider.health = await getProviderHealth(provider);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
debugLogger.error('Error getting provider health', {
|
|
40
|
+
provider: provider.id,
|
|
41
|
+
error: error.message
|
|
42
|
+
});
|
|
43
|
+
provider.health = { status: 'error', errors: [error.message] };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return providers;
|
|
48
|
+
} catch (err) {
|
|
49
|
+
debugLogger.error('Error loading providers data', { error: err.message });
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Build provider choice items with enhanced health and quota info
|
|
56
|
+
*/
|
|
57
|
+
function buildProviderChoices(providers, preferences = {}) {
|
|
58
|
+
if (!providers || !providers.length) {
|
|
59
|
+
return [{ name: chalk.gray(' (no providers found)'), value: 'noop' }];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return providers.map((provider, index) => {
|
|
63
|
+
const isEnabled = preferences.enabled && preferences.enabled[provider.id] !== false;
|
|
64
|
+
const status = isEnabled ? chalk.green('ENABLED') : chalk.red('DISABLED');
|
|
65
|
+
const icon = getStatusIcon(isEnabled);
|
|
66
|
+
|
|
67
|
+
// Enhanced health information
|
|
68
|
+
let healthInfo = '';
|
|
69
|
+
if (provider.health) {
|
|
70
|
+
const healthIcon = provider.health.status === 'healthy' ? 'š¢' :
|
|
71
|
+
provider.health.status === 'degraded' ? 'š”' :
|
|
72
|
+
provider.health.status === 'unhealthy' ? 'š“' :
|
|
73
|
+
provider.health.status === 'error' ? 'ā' : 'āŖ';
|
|
74
|
+
healthInfo = ` ${healthIcon}`;
|
|
75
|
+
|
|
76
|
+
if (provider.health.responseTime) {
|
|
77
|
+
const timeColor = provider.health.responseTime < 1000 ? chalk.green :
|
|
78
|
+
provider.health.responseTime < 3000 ? chalk.yellow : chalk.red;
|
|
79
|
+
healthInfo += ` ${timeColor(provider.health.responseTime + 'ms')}`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Enhanced quota information
|
|
84
|
+
let quotaInfo = '';
|
|
85
|
+
if (provider.health && provider.health.quota) {
|
|
86
|
+
const quota = provider.health.quota;
|
|
87
|
+
const percentage = Math.round((quota.used / quota.limit) * 100);
|
|
88
|
+
const color = percentage >= 90 ? chalk.red : percentage >= 75 ? chalk.yellow : chalk.green;
|
|
89
|
+
quotaInfo = ` ${color(quota.used + '/' + quota.limit)}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Enhanced IDE detection
|
|
93
|
+
let ideInfo = '';
|
|
94
|
+
if (provider.health && provider.health.ideDetection) {
|
|
95
|
+
const ideCount = Object.values(provider.health.ideDetection)
|
|
96
|
+
.filter(ide => ide.installed).length;
|
|
97
|
+
if (ideCount > 0) {
|
|
98
|
+
ideInfo = ` š»${ideCount}`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const displayName = provider.name || provider.type || provider.id || 'Unknown';
|
|
103
|
+
const details = [status, healthInfo, quotaInfo, ideInfo].filter(Boolean).join(' ');
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
type: 'provider',
|
|
107
|
+
name: `${icon} ${displayName}${details ? ' - ' + details : ''}`,
|
|
108
|
+
value: `provider:${index}`
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Show provider manager with full functionality
|
|
115
|
+
*/
|
|
116
|
+
async function showProviderManager(navigation) {
|
|
117
|
+
debugLogger.info('showProviderManager called');
|
|
118
|
+
|
|
119
|
+
const data = await loadProvidersData(navigation);
|
|
120
|
+
if (!data || !data.providers) {
|
|
121
|
+
console.log(chalk.yellow('No providers data available'));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const { providers, preferences } = data;
|
|
126
|
+
|
|
127
|
+
while (true) {
|
|
128
|
+
// Build menu items
|
|
129
|
+
const items = buildProviderChoices(providers, preferences);
|
|
130
|
+
|
|
131
|
+
// Add actions
|
|
132
|
+
items.push({ type: 'blank', name: '', value: 'blank' });
|
|
133
|
+
items.push({ type: 'action', name: '[Test All Providers]', value: 'test-all' });
|
|
134
|
+
items.push({ type: 'action', name: '[Refresh Health Status]', value: 'refresh-health' });
|
|
135
|
+
items.push({ type: 'action', name: '[Kiro Integration]', value: 'kiro-status' });
|
|
136
|
+
items.push({ type: 'action', name: 'Exit', value: 'exit' });
|
|
137
|
+
|
|
138
|
+
// Show menu with extra keys for provider actions
|
|
139
|
+
const result = await showQuickMenu(items, 0, {
|
|
140
|
+
extraKeys: (str, key, selectedIndex, context) => {
|
|
141
|
+
const currentItem = items[selectedIndex];
|
|
142
|
+
|
|
143
|
+
// Handle enable/disable with e/d keys
|
|
144
|
+
if ((str === 'e' || str === 'E') && currentItem && currentItem.value && currentItem.value.startsWith('provider:')) {
|
|
145
|
+
const providerIndex = parseInt(currentItem.value.substring(9), 10);
|
|
146
|
+
context.resolveWith(`provider-enable:${providerIndex}`);
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if ((str === 'd' || str === 'D') && currentItem && currentItem.value && currentItem.value.startsWith('provider:')) {
|
|
151
|
+
const providerIndex = parseInt(currentItem.value.substring(9), 10);
|
|
152
|
+
context.resolveWith(`provider-disable:${providerIndex}`);
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Handle re-ordering with arrow keys when on provider
|
|
157
|
+
if ((key.name === 'up' || key.name === 'down') && currentItem && currentItem.value && currentItem.value.startsWith('provider:')) {
|
|
158
|
+
const providerIndex = parseInt(currentItem.value.substring(9), 10);
|
|
159
|
+
|
|
160
|
+
if (key.name === 'up' && providerIndex > 0) {
|
|
161
|
+
context.resolveWith(`provider-move-up:${providerIndex}`);
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (key.name === 'down' && providerIndex < providers.length - 1) {
|
|
166
|
+
context.resolveWith(`provider-move-down:${providerIndex}`);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return false;
|
|
172
|
+
},
|
|
173
|
+
hintText: 'e/d=enable/disable āā=re-order Enter=details ā=back'
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Handle actions
|
|
177
|
+
if (result.value === 'test-all') {
|
|
178
|
+
await testAllProviders(providers);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (result.value === 'refresh-health') {
|
|
183
|
+
await refreshProviderHealth(providers, navigation);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (result.value === 'kiro-status') {
|
|
188
|
+
await showKiroIntegrationStatus();
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (result.value.startsWith('provider-enable:')) {
|
|
193
|
+
const providerIndex = parseInt(result.value.substring(16), 10);
|
|
194
|
+
await toggleProvider(providers[providerIndex], true, navigation);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (result.value.startsWith('provider-disable:')) {
|
|
199
|
+
const providerIndex = parseInt(result.value.substring(17), 10);
|
|
200
|
+
await toggleProvider(providers[providerIndex], false, navigation);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (result.value.startsWith('provider-move-up:')) {
|
|
205
|
+
const providerIndex = parseInt(result.value.substring(18), 10);
|
|
206
|
+
await moveProvider(providers, providerIndex, 'up', navigation);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (result.value.startsWith('provider-move-down:')) {
|
|
211
|
+
const providerIndex = parseInt(result.value.substring(20), 10);
|
|
212
|
+
await moveProvider(providers, providerIndex, 'down', navigation);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (result.value === '__cancel__') {
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Toggle provider enabled/disabled status
|
|
224
|
+
*/
|
|
225
|
+
async function toggleProvider(provider, enabled, navigation) {
|
|
226
|
+
try {
|
|
227
|
+
const result = navigation.resolver.resolve('update provider', {
|
|
228
|
+
providerId: provider.id,
|
|
229
|
+
enabled
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
if (result.success) {
|
|
233
|
+
await result.command.execute();
|
|
234
|
+
console.log(chalk.green(`\nā ${provider.name || provider.id} ${enabled ? 'enabled' : 'disabled'}`));
|
|
235
|
+
}
|
|
236
|
+
} catch (err) {
|
|
237
|
+
console.log(chalk.red(`Error toggling provider: ${err.message}`));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Move provider up/down in order
|
|
243
|
+
*/
|
|
244
|
+
async function moveProvider(providers, index, direction, navigation) {
|
|
245
|
+
if (direction === 'up' && index > 0) {
|
|
246
|
+
// Swap with previous
|
|
247
|
+
const temp = providers[index - 1];
|
|
248
|
+
providers[index - 1] = providers[index];
|
|
249
|
+
providers[index] = temp;
|
|
250
|
+
} else if (direction === 'down' && index < providers.length - 1) {
|
|
251
|
+
// Swap with next
|
|
252
|
+
const temp = providers[index + 1];
|
|
253
|
+
providers[index + 1] = providers[index];
|
|
254
|
+
providers[index] = temp;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
// Save new order
|
|
259
|
+
const newOrder = providers.map(p => p.id);
|
|
260
|
+
const result = navigation.resolver.resolve('update provider-order', { order: newOrder });
|
|
261
|
+
|
|
262
|
+
if (result.success) {
|
|
263
|
+
await result.command.execute();
|
|
264
|
+
console.log(chalk.green('\nā Provider order updated'));
|
|
265
|
+
}
|
|
266
|
+
} catch (err) {
|
|
267
|
+
console.log(chalk.red(`Error updating provider order: ${err.message}`));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Test connectivity of all providers
|
|
273
|
+
*/
|
|
274
|
+
async function testAllProviders(providers) {
|
|
275
|
+
console.log(chalk.cyan('\nš Testing all providers...\n'));
|
|
276
|
+
|
|
277
|
+
for (const provider of providers) {
|
|
278
|
+
console.log(chalk.gray(`Testing ${provider.name || provider.id}...`));
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
const result = await fetch(`https://api.openai.com/v1/models`, {
|
|
282
|
+
timeout: 5000,
|
|
283
|
+
headers: provider.apiKey ? { 'Authorization': `Bearer ${provider.apiKey}` } : {}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
if (result.ok) {
|
|
287
|
+
console.log(chalk.green(` ā ${provider.name || provider.id} - Connected`));
|
|
288
|
+
} else {
|
|
289
|
+
console.log(chalk.red(` ā ${provider.name || provider.id} - Failed (${result.status})`));
|
|
290
|
+
}
|
|
291
|
+
} catch (err) {
|
|
292
|
+
console.log(chalk.red(` ā ${provider.name || provider.id} - Error (${err.message})`));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
console.log(chalk.gray('\nPress Enter to continue...'));
|
|
297
|
+
await new Promise(resolve => {
|
|
298
|
+
process.stdin.once('data', resolve);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Refresh provider health status
|
|
304
|
+
*/
|
|
305
|
+
async function refreshProviderHealth(providers, navigation) {
|
|
306
|
+
console.log(chalk.cyan('\nš Refreshing provider health...\n'));
|
|
307
|
+
|
|
308
|
+
for (const provider of providers) {
|
|
309
|
+
try {
|
|
310
|
+
// Test basic connectivity
|
|
311
|
+
const result = await fetch(`https://api.openai.com/v1/models`, {
|
|
312
|
+
timeout: 3000,
|
|
313
|
+
headers: provider.apiKey ? { 'Authorization': `Bearer ${provider.apiKey}` } : {}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
if (result.ok) {
|
|
317
|
+
console.log(chalk.green(` ā ${provider.name || provider.id} - Healthy`));
|
|
318
|
+
} else {
|
|
319
|
+
console.log(chalk.yellow(` ā ${provider.name || provider.id} - Limited (${result.status})`));
|
|
320
|
+
}
|
|
321
|
+
} catch (err) {
|
|
322
|
+
console.log(chalk.red(` ā ${provider.name || provider.id} - Unreachable`));
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
console.log(chalk.gray('\nPress Enter to continue...'));
|
|
327
|
+
await new Promise(resolve => {
|
|
328
|
+
process.stdin.once('data', resolve);
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Show Kiro integration status
|
|
334
|
+
*/
|
|
335
|
+
async function showKiroIntegrationStatus() {
|
|
336
|
+
console.clear();
|
|
337
|
+
console.log(chalk.bold.cyan('š Kiro Integration Status\n'));
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
const kiroStatus = await getKiroIntegrationStatus();
|
|
341
|
+
|
|
342
|
+
console.log(`Status: ${kiroStatus.installed ? chalk.green('Installed') : chalk.red('Not Installed')}`);
|
|
343
|
+
|
|
344
|
+
if (kiroStatus.version) {
|
|
345
|
+
console.log(`Version: ${chalk.white(kiroStatus.version)}`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
console.log(`Configured: ${kiroStatus.configured ? chalk.green('Yes') : chalk.yellow('No')}`);
|
|
349
|
+
|
|
350
|
+
if (kiroStatus.config) {
|
|
351
|
+
console.log(`Project: ${chalk.white(kiroStatus.config.project)}`);
|
|
352
|
+
console.log(`Provider: ${chalk.white(kiroStatus.config.provider)}`);
|
|
353
|
+
console.log(`Auto-mode: ${kiroStatus.config.autoMode ? chalk.green('Enabled') : chalk.red('Disabled')}`);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (!kiroStatus.installed) {
|
|
357
|
+
console.log(chalk.yellow('\nš” Kiro integration enhances provider management with advanced features.'));
|
|
358
|
+
console.log(chalk.gray('Visit https://github.com/kiro-dev/kiro to install.'));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
} catch (error) {
|
|
362
|
+
console.log(chalk.red('Error checking Kiro status: ' + error.message));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
console.log(chalk.gray('\nPress Enter to continue...'));
|
|
366
|
+
await new Promise(resolve => {
|
|
367
|
+
process.stdin.once('data', resolve);
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
module.exports = {
|
|
372
|
+
showProviderManager,
|
|
373
|
+
loadProvidersData,
|
|
374
|
+
buildProviderChoices,
|
|
375
|
+
showKiroIntegrationStatus
|
|
376
|
+
};
|
|
@@ -55,7 +55,7 @@ function getVisualLineCount(text) {
|
|
|
55
55
|
* Determine whether an item can receive keyboard focus
|
|
56
56
|
*/
|
|
57
57
|
function isSelectable(item) {
|
|
58
|
-
return item.type !== 'blank' && item.type !== 'info';
|
|
58
|
+
return item.type !== 'blank' && item.type !== 'info' && item.type !== 'separator';
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
@@ -88,6 +88,27 @@ function renderMenu(items, selectedIndex, hintText) {
|
|
|
88
88
|
return;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
// 'separator' type: gray text header, not selectable
|
|
92
|
+
if (item.type === 'separator') {
|
|
93
|
+
output = chalk.gray(` ${item.name}`);
|
|
94
|
+
process.stdout.write(output + '\n');
|
|
95
|
+
linesPrinted += getVisualLineCount(` ${item.name}`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 'sub-item' type: selectable sub-items with arrow navigation, no letter shortcuts
|
|
100
|
+
if (item.type === 'sub-item') {
|
|
101
|
+
const raw = `${isSelected ? '⯠' : ' '}${item.name}`;
|
|
102
|
+
if (isSelected) {
|
|
103
|
+
output = chalk.cyan(`⯠${item.name}`);
|
|
104
|
+
} else {
|
|
105
|
+
output = ` ${item.name}`;
|
|
106
|
+
}
|
|
107
|
+
process.stdout.write(output + '\n');
|
|
108
|
+
linesPrinted += getVisualLineCount(raw);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
91
112
|
// 'header' type: no letter, but IS selectable (focusable with āā, Enter returns value)
|
|
92
113
|
if (item.type === 'header') {
|
|
93
114
|
const raw = `${isSelected ? '⯠' : ' '}${item.name}`;
|
|
@@ -194,6 +215,9 @@ function showQuickMenu(items, initialSelectedIndex = 0, options = {}) {
|
|
|
194
215
|
// Move cursor back up to start of menu and clear downward
|
|
195
216
|
readline.moveCursor(process.stdout, 0, -lastLinesPrinted);
|
|
196
217
|
readline.clearScreenDown(process.stdout);
|
|
218
|
+
} else {
|
|
219
|
+
// On first render, clear from current position down to ensure clean start
|
|
220
|
+
readline.clearScreenDown(process.stdout);
|
|
197
221
|
}
|
|
198
222
|
lastLinesPrinted = renderMenu(items, selectedIndex, hintText);
|
|
199
223
|
isFirstRender = false;
|