vibecodingmachine-cli 2026.1.29-713 → 2026.2.20-423
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/vibecodingmachine.js +124 -0
- package/package.json +3 -2
- package/src/commands/agents-check.js +69 -0
- package/src/commands/auto-direct.js +930 -145
- package/src/commands/auto.js +26 -4
- package/src/commands/ide.js +2 -1
- package/src/commands/requirements.js +23 -27
- package/src/utils/auto-mode.js +4 -1
- package/src/utils/cline-js-handler.js +218 -0
- package/src/utils/config.js +22 -0
- package/src/utils/display-formatters-complete.js +229 -0
- package/src/utils/display-formatters-extracted.js +219 -0
- package/src/utils/display-formatters.js +157 -0
- package/src/utils/feedback-handler.js +143 -0
- package/src/utils/ide-detection-complete.js +126 -0
- package/src/utils/ide-detection-extracted.js +116 -0
- package/src/utils/ide-detection.js +124 -0
- package/src/utils/interactive-backup.js +5664 -0
- package/src/utils/interactive-broken.js +280 -0
- package/src/utils/interactive.js +31 -5534
- package/src/utils/provider-checker.js +410 -0
- package/src/utils/provider-manager.js +251 -0
- package/src/utils/provider-registry.js +18 -9
- package/src/utils/requirement-actions.js +884 -0
- package/src/utils/requirements-navigator.js +585 -0
- package/src/utils/rui-trui-adapter.js +311 -0
- package/src/utils/simple-trui.js +204 -0
- package/src/utils/status-helpers-extracted.js +125 -0
- package/src/utils/status-helpers.js +107 -0
- package/src/utils/trui-debug.js +261 -0
- package/src/utils/trui-feedback.js +133 -0
- package/src/utils/trui-nav-agents.js +119 -0
- package/src/utils/trui-nav-requirements.js +268 -0
- package/src/utils/trui-nav-settings.js +157 -0
- package/src/utils/trui-nav-specifications.js +139 -0
- package/src/utils/trui-navigation.js +303 -0
- package/src/utils/trui-provider-manager.js +182 -0
- package/src/utils/trui-quick-menu.js +365 -0
- package/src/utils/trui-req-actions.js +372 -0
- package/src/utils/trui-req-tree.js +534 -0
- package/src/utils/trui-specifications.js +359 -0
- package/src/utils/trui-text-editor.js +350 -0
- package/src/utils/trui-windsurf.js +336 -0
- package/src/utils/welcome-screen-extracted.js +135 -0
- package/src/utils/welcome-screen.js +134 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TRUI Navigation — Main Menu
|
|
3
|
+
*
|
|
4
|
+
* Renders the status-rich main menu using showQuickMenu (raw keypress).
|
|
5
|
+
* Status/settings lines shown in gray at top; action items in white below.
|
|
6
|
+
* Dispatches to: Requirements tree, Provider manager, Settings, Spec creation.
|
|
7
|
+
* Now using RUI pattern for consistent command structure.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const { showQuickMenu } = require('./trui-quick-menu');
|
|
12
|
+
const { initializeWindsurfIntegration, getWindsurfStatus } = require('./trui-windsurf');
|
|
13
|
+
const { debugLogger } = require('./trui-debug');
|
|
14
|
+
const RUITRUIAdapter = require('./rui-trui-adapter');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build main-menu items array with live status at top
|
|
18
|
+
*/
|
|
19
|
+
async function buildMainMenuItems() {
|
|
20
|
+
const items = [];
|
|
21
|
+
|
|
22
|
+
// Auto Mode status
|
|
23
|
+
let autoStatus = { running: false };
|
|
24
|
+
try {
|
|
25
|
+
const { checkAutoModeStatus } = require('./auto-mode');
|
|
26
|
+
autoStatus = await checkAutoModeStatus();
|
|
27
|
+
} catch (_) {}
|
|
28
|
+
|
|
29
|
+
items.push({
|
|
30
|
+
type: 'setting',
|
|
31
|
+
name: `Auto Mode: ${autoStatus.running ? chalk.green('Running ✓') : chalk.yellow('Stopped ○')}`,
|
|
32
|
+
value: 'setting:auto',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Requirements summary
|
|
36
|
+
try {
|
|
37
|
+
const { countRequirements } = require('./status-helpers-extracted');
|
|
38
|
+
const counts = await countRequirements();
|
|
39
|
+
if (counts) {
|
|
40
|
+
const total = (counts.todoCount || 0) + (counts.toVerifyCount || 0) + (counts.verifiedCount || 0);
|
|
41
|
+
const pct = n => total > 0 ? Math.round((n / total) * 100) : 0;
|
|
42
|
+
items.push({
|
|
43
|
+
type: 'setting',
|
|
44
|
+
name: `Requirements: ${chalk.yellow(counts.todoCount + ' todo')}, ${chalk.cyan(counts.toVerifyCount + ' verify')}, ${chalk.green(counts.verifiedCount + ' done')} (${pct(counts.verifiedCount)}% complete)`,
|
|
45
|
+
value: 'setting:requirements',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
} catch (_) {}
|
|
49
|
+
|
|
50
|
+
items.push({ type: 'blank', name: '', value: 'blank' });
|
|
51
|
+
|
|
52
|
+
// Action items
|
|
53
|
+
items.push({ type: 'action', name: '📋 Requirements', value: 'requirements' });
|
|
54
|
+
items.push({ type: 'action', name: '🤖 Agents / Providers', value: 'agents' });
|
|
55
|
+
items.push({ type: 'action', name: '⚙️ Settings', value: 'settings' });
|
|
56
|
+
items.push({ type: 'action', name: '[+ Add Requirement]', value: 'add-req' });
|
|
57
|
+
items.push({ type: 'action', name: '[+ Add Specification]', value: 'add-spec' });
|
|
58
|
+
items.push({ type: 'action', name: '💬 Send Continue to Windsurf', value: 'continue-windsurf' });
|
|
59
|
+
items.push({ type: 'action', name: '🔄 Sync Now', value: 'sync' });
|
|
60
|
+
|
|
61
|
+
return items;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* TRUINavigation — main menu loop
|
|
66
|
+
*/
|
|
67
|
+
class TRUINavigation {
|
|
68
|
+
constructor() {
|
|
69
|
+
this._lastIndex = 0;
|
|
70
|
+
this.ruiAdapter = new RUITRUIAdapter();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async start() {
|
|
74
|
+
const { showWelcomeScreen } = require('./welcome-screen-extracted');
|
|
75
|
+
console.clear();
|
|
76
|
+
try { await showWelcomeScreen(); } catch (_) {}
|
|
77
|
+
|
|
78
|
+
// Initialize Windsurf integration with error handling
|
|
79
|
+
try {
|
|
80
|
+
initializeWindsurfIntegration();
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.log(chalk.yellow('⚠ Windsurf integration disabled: ' + error.message));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
await this._loop();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async _loop() {
|
|
89
|
+
const {
|
|
90
|
+
showRequirementsTree,
|
|
91
|
+
} = require('./trui-req-tree');
|
|
92
|
+
const { showProviderManagerMenu } = require('./trui-provider-manager');
|
|
93
|
+
const { showSettings } = require('./trui-nav-settings');
|
|
94
|
+
const { addRequirementFlow } = require('./trui-req-actions');
|
|
95
|
+
const { addSpecificationFlow } = require('./trui-nav-specifications');
|
|
96
|
+
const { sendContinueToWindsurf } = require('./trui-windsurf');
|
|
97
|
+
const { showFeedbackSubmission } = require('./trui-feedback');
|
|
98
|
+
const { showSpecificationsList } = require('./trui-specifications');
|
|
99
|
+
|
|
100
|
+
while (true) {
|
|
101
|
+
try {
|
|
102
|
+
const items = await buildMainMenuItems();
|
|
103
|
+
const result = await showQuickMenu(items, this._lastIndex);
|
|
104
|
+
this._lastIndex = result.selectedIndex;
|
|
105
|
+
const action = result.value;
|
|
106
|
+
|
|
107
|
+
if (action === '__cancel__' || action === 'exit') {
|
|
108
|
+
await this._confirmExit();
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Dispatch to sub-modules
|
|
113
|
+
try {
|
|
114
|
+
if (action === 'setting:auto') {
|
|
115
|
+
try {
|
|
116
|
+
const { checkAutoModeStatus, stopAutoMode } = require('./auto-mode');
|
|
117
|
+
const s = await checkAutoModeStatus();
|
|
118
|
+
if (s.running) {
|
|
119
|
+
await stopAutoMode('manual');
|
|
120
|
+
console.log(chalk.yellow('\n⏹ Auto mode stopped\n'));
|
|
121
|
+
await this._pause();
|
|
122
|
+
console.clear();
|
|
123
|
+
} else {
|
|
124
|
+
console.log(chalk.bold.cyan('\n▶ Starting Auto Mode...\n'));
|
|
125
|
+
try {
|
|
126
|
+
const { getAutoConfig } = require('./config');
|
|
127
|
+
const currentConfig = await getAutoConfig();
|
|
128
|
+
// Pick first enabled provider
|
|
129
|
+
const { getProviderPreferences } = require('./provider-registry');
|
|
130
|
+
const prefs = await getProviderPreferences();
|
|
131
|
+
let agentToUse = currentConfig.ide || currentConfig.agent || 'cline';
|
|
132
|
+
for (const agentId of (prefs.order || [])) {
|
|
133
|
+
if (prefs.enabled[agentId] !== false) { agentToUse = agentId; break; }
|
|
134
|
+
}
|
|
135
|
+
// Release raw mode before running auto (so Ctrl+C works)
|
|
136
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
137
|
+
process.stdin.setRawMode(false);
|
|
138
|
+
}
|
|
139
|
+
const options = { ide: agentToUse };
|
|
140
|
+
if (currentConfig.neverStop) {
|
|
141
|
+
options.neverStop = true;
|
|
142
|
+
} else if (currentConfig.maxChats) {
|
|
143
|
+
options.maxChats = currentConfig.maxChats;
|
|
144
|
+
} else {
|
|
145
|
+
options.neverStop = true;
|
|
146
|
+
}
|
|
147
|
+
const { handleAutoStart } = require('../commands/auto-direct');
|
|
148
|
+
await handleAutoStart(options);
|
|
149
|
+
} catch (startErr) {
|
|
150
|
+
if (startErr.message && startErr.message.includes('User force closed')) {
|
|
151
|
+
console.log(chalk.yellow('\nCancelled\n'));
|
|
152
|
+
} else {
|
|
153
|
+
console.log(chalk.red(`\n✗ Error: ${startErr.message}`));
|
|
154
|
+
if (startErr.stack) console.log(chalk.gray(startErr.stack.split('\n').slice(0, 8).join('\n')));
|
|
155
|
+
console.log(chalk.yellow('\nReturning to menu in 5 seconds...'));
|
|
156
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
await this._pause();
|
|
160
|
+
console.clear();
|
|
161
|
+
}
|
|
162
|
+
} catch (err) { console.log(chalk.red('Error: ' + err.message)); await this._pause(); console.clear(); }
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (action === 'requirements' || action === 'setting:requirements') {
|
|
167
|
+
try { await showRequirementsTree(); } catch (err) { console.log(chalk.red('Requirements error: ' + err.message)); await this._pause(); }
|
|
168
|
+
console.clear();
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (action === 'agents') {
|
|
173
|
+
try { await showProviderManagerMenu(); } catch (err) { console.log(chalk.red('Agents error: ' + err.message)); await this._pause(); }
|
|
174
|
+
console.clear();
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (action === 'settings') {
|
|
179
|
+
try { await showSettings(); } catch (err) { console.log(chalk.red('Settings error: ' + err.message)); await this._pause(); }
|
|
180
|
+
console.clear();
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (action === 'add-req') {
|
|
185
|
+
try { await addRequirementFlow(); } catch (err) { console.log(chalk.red('Error: ' + err.message)); await this._pause(); }
|
|
186
|
+
console.clear();
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (action === 'add-spec') {
|
|
191
|
+
try { await addSpecificationFlow(); } catch (err) { console.log(chalk.red('Error: ' + err.message)); await this._pause(); }
|
|
192
|
+
console.clear();
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (action === 'continue-windsurf') {
|
|
197
|
+
try {
|
|
198
|
+
await sendContinueToWindsurf();
|
|
199
|
+
console.log(chalk.green('\n✓ Continue message sent to Windsurf Cascade\n'));
|
|
200
|
+
} catch (err) { console.log(chalk.red('Error: ' + err.message)); }
|
|
201
|
+
await this._pause();
|
|
202
|
+
console.clear();
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (action === 'sync') {
|
|
207
|
+
try {
|
|
208
|
+
const { execSync } = require('child_process');
|
|
209
|
+
console.log(chalk.cyan('\n🔄 Syncing...\n'));
|
|
210
|
+
execSync('vcm sync:now', { stdio: 'inherit' });
|
|
211
|
+
} catch (_) { console.log(chalk.gray('vcm sync:now: command unavailable')); }
|
|
212
|
+
await this._pause();
|
|
213
|
+
console.clear();
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Unknown action — show error
|
|
218
|
+
console.log(chalk.yellow('Unknown action: ' + action));
|
|
219
|
+
await this._pause();
|
|
220
|
+
console.clear();
|
|
221
|
+
continue;
|
|
222
|
+
} catch (err) {
|
|
223
|
+
console.log(chalk.red('Action error: ' + err.message));
|
|
224
|
+
await this._pause();
|
|
225
|
+
console.clear();
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
} catch (error) {
|
|
230
|
+
debugLogger.error('Menu loop error', { error: error.message, stack: error.stack });
|
|
231
|
+
console.log(chalk.red('Menu error: ' + error.message));
|
|
232
|
+
await this._pause();
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async _confirmExit() {
|
|
239
|
+
console.clear();
|
|
240
|
+
console.log(chalk.yellow('\nExit Vibe Coding Machine? (Y/n/x)\n'));
|
|
241
|
+
|
|
242
|
+
return new Promise((resolve) => {
|
|
243
|
+
const readline = require('readline');
|
|
244
|
+
|
|
245
|
+
// Set up raw keypress handling
|
|
246
|
+
readline.emitKeypressEvents(process.stdin);
|
|
247
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
248
|
+
process.stdin.setRawMode(true);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const onKeypress = (str, key) => {
|
|
252
|
+
// Handle left arrow - exit without confirmation
|
|
253
|
+
if (key && key.name === 'left') {
|
|
254
|
+
cleanup();
|
|
255
|
+
console.log(chalk.yellow('\nGoodbye!\n'));
|
|
256
|
+
process.exit(0);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Handle Enter or 'y' or 'Y' or 'x' - confirm exit
|
|
261
|
+
if (key && (key.name === 'return' || (str && (str === 'y' || str === 'Y' || str === 'x' || str === 'X')))) {
|
|
262
|
+
cleanup();
|
|
263
|
+
console.log(chalk.yellow('\nGoodbye!\n'));
|
|
264
|
+
process.exit(0);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Handle 'n' or 'N' or ESC or right arrow - cancel exit
|
|
269
|
+
if (key && (key.name === 'escape' || key.name === 'right' || (str && (str === 'n' || str === 'N')))) {
|
|
270
|
+
cleanup();
|
|
271
|
+
resolve();
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const cleanup = () => {
|
|
277
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
278
|
+
process.stdin.setRawMode(false);
|
|
279
|
+
}
|
|
280
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
281
|
+
process.stdin.pause();
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
process.stdin.on('keypress', onKeypress);
|
|
285
|
+
process.stdin.resume();
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async _pause() {
|
|
290
|
+
const inquirer = require('inquirer');
|
|
291
|
+
await inquirer.prompt([{ type: 'input', name: 'c', message: chalk.gray('Press Enter to continue...') }]);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async promptContinue() {
|
|
295
|
+
await this._pause();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
module.exports = { TRUINavigation, buildMainMenuItems };
|
|
300
|
+
|
|
301
|
+
// Export a singleton instance for use by other modules
|
|
302
|
+
const navigationInstance = new TRUINavigation();
|
|
303
|
+
module.exports.promptContinue = () => navigationInstance.promptContinue();
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TRUI Provider / Agent Manager
|
|
3
|
+
*
|
|
4
|
+
* Lists all configured providers with letter shortcuts via showQuickMenu.
|
|
5
|
+
* Changes are saved immediately on every toggle or reorder.
|
|
6
|
+
*
|
|
7
|
+
* Consistent key scheme (all non-letter commands):
|
|
8
|
+
* ↑ / ↓ — navigate
|
|
9
|
+
* < /, — move item down (lower priority / higher index), NO SHIFT needed
|
|
10
|
+
* > /. — move item up (higher priority / lower index), NO SHIFT needed
|
|
11
|
+
* Space — toggle enable/disable
|
|
12
|
+
* Enter — set as active provider
|
|
13
|
+
* ← / ESC — back
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const chalk = require('chalk');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Main provider manager menu — uses showQuickMenu for letter shortcuts + consistency
|
|
20
|
+
*/
|
|
21
|
+
async function showProviderManagerMenu() {
|
|
22
|
+
const { showQuickMenu } = require('./trui-quick-menu');
|
|
23
|
+
const {
|
|
24
|
+
getProviderDefinitions,
|
|
25
|
+
getProviderPreferences,
|
|
26
|
+
saveProviderPreferences,
|
|
27
|
+
} = require('./provider-registry');
|
|
28
|
+
const { IDEHealthTracker, HealthReporter } = require('vibecodingmachine-core');
|
|
29
|
+
|
|
30
|
+
const definitions = getProviderDefinitions();
|
|
31
|
+
const defMap = new Map(definitions.map(def => [def.id, def]));
|
|
32
|
+
const prefs = await getProviderPreferences();
|
|
33
|
+
let order = prefs.order.slice();
|
|
34
|
+
let enabled = { ...prefs.enabled };
|
|
35
|
+
let lastIndex = 0;
|
|
36
|
+
|
|
37
|
+
// Ensure all definitions are present in order
|
|
38
|
+
for (const def of definitions) {
|
|
39
|
+
if (!order.includes(def.id)) order.push(def.id);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Load health metrics (best-effort — empty map if unavailable)
|
|
43
|
+
let allMetrics = new Map();
|
|
44
|
+
try {
|
|
45
|
+
const tracker = new IDEHealthTracker();
|
|
46
|
+
allMetrics = await tracker.getAllHealthMetrics();
|
|
47
|
+
} catch (_) {}
|
|
48
|
+
|
|
49
|
+
const printHeader = () => {
|
|
50
|
+
console.clear();
|
|
51
|
+
process.stdout.write(chalk.bold.cyan('🤖 Agents & Providers\n\n'));
|
|
52
|
+
process.stdout.write(chalk.gray(' Interface types: 🖥️ GUI Automation ⚡ Direct Automation\n'));
|
|
53
|
+
process.stdout.write(chalk.gray(' Provider type: ☁️ Cloud 🏠 Local\n\n'));
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const buildItems = () => {
|
|
57
|
+
return order.map((id, idx) => {
|
|
58
|
+
const def = defMap.get(id);
|
|
59
|
+
if (!def) return null;
|
|
60
|
+
const isEnabled = enabled[id] !== false;
|
|
61
|
+
const statusEmoji = isEnabled ? '🟢' : '🔴';
|
|
62
|
+
const interfaceIcon = def.type === 'ide' ? '🖥️ ' : '⚡';
|
|
63
|
+
const providerTypeIcon =
|
|
64
|
+
def.type === 'direct' && def.category === 'llm' && def.id === 'ollama'
|
|
65
|
+
? '🏠'
|
|
66
|
+
: '☁️ ';
|
|
67
|
+
const nameStr = isEnabled ? def.name : chalk.gray(def.name);
|
|
68
|
+
|
|
69
|
+
// Health stats
|
|
70
|
+
const metrics = allMetrics.get(id);
|
|
71
|
+
let healthStr = '';
|
|
72
|
+
if (metrics && metrics.totalInteractions > 0) {
|
|
73
|
+
const healthIcon = metrics.consecutiveFailures === 0 ? '✅' : '⚠️ ';
|
|
74
|
+
const counters = HealthReporter.formatInlineCounters(metrics);
|
|
75
|
+
healthStr = ' ' + healthIcon + (counters ? ' ' + chalk.gray(counters) : '');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
type: 'action',
|
|
80
|
+
name: `${statusEmoji} ${interfaceIcon} ${providerTypeIcon} ${nameStr} ${chalk.gray('(' + id + ')')}${healthStr}`,
|
|
81
|
+
value: `provider:${idx}`,
|
|
82
|
+
};
|
|
83
|
+
}).filter(Boolean);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const hintText = '[< move item down > move item up Space toggle Enter select ← back, NO SHIFT needed]';
|
|
87
|
+
|
|
88
|
+
printHeader();
|
|
89
|
+
|
|
90
|
+
while (true) {
|
|
91
|
+
const items = buildItems();
|
|
92
|
+
|
|
93
|
+
const extraKeys = (str, key, selectedIndex, { resolveWith }) => {
|
|
94
|
+
const keyName = key && key.name;
|
|
95
|
+
|
|
96
|
+
// Space: toggle enable/disable
|
|
97
|
+
if (str === ' ' || keyName === 'space') {
|
|
98
|
+
const item = items[selectedIndex];
|
|
99
|
+
if (item && item.value && item.value.startsWith('provider:')) {
|
|
100
|
+
resolveWith(`toggle:${selectedIndex}`);
|
|
101
|
+
}
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// < or , = move item down (no shift needed); > or . = move item up
|
|
106
|
+
const isDown = str === ',' || str === '<' || keyName === ',' || keyName === '<';
|
|
107
|
+
const isUp = str === '.' || str === '>' || keyName === '.' || keyName === '>';
|
|
108
|
+
if (isDown || isUp) {
|
|
109
|
+
const item = items[selectedIndex];
|
|
110
|
+
if (item && item.value && item.value.startsWith('provider:')) {
|
|
111
|
+
resolveWith(`reorder:${isDown ? 'down' : 'up'}:${selectedIndex}`);
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return false;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const result = await showQuickMenu(items, lastIndex, { extraKeys, hintText });
|
|
120
|
+
lastIndex = result.selectedIndex;
|
|
121
|
+
const value = result.value;
|
|
122
|
+
|
|
123
|
+
// Back (← / ESC)
|
|
124
|
+
if (value === '__cancel__') break;
|
|
125
|
+
|
|
126
|
+
// Refresh health metrics on each action so stats stay current
|
|
127
|
+
try {
|
|
128
|
+
const tracker = new IDEHealthTracker();
|
|
129
|
+
allMetrics = await tracker.getAllHealthMetrics();
|
|
130
|
+
} catch (_) {}
|
|
131
|
+
|
|
132
|
+
// Toggle enable/disable — save immediately
|
|
133
|
+
if (value.startsWith('toggle:')) {
|
|
134
|
+
const itemIdx = parseInt(value.split(':')[1], 10);
|
|
135
|
+
const item = items[itemIdx];
|
|
136
|
+
if (item && item.value && item.value.startsWith('provider:')) {
|
|
137
|
+
const provIdx = parseInt(item.value.split(':')[1], 10);
|
|
138
|
+
const id = order[provIdx];
|
|
139
|
+
if (id) {
|
|
140
|
+
enabled[id] = enabled[id] === false ? true : false;
|
|
141
|
+
await saveProviderPreferences(order, enabled);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
printHeader();
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Reorder — save immediately
|
|
149
|
+
if (value.startsWith('reorder:')) {
|
|
150
|
+
const parts = value.split(':');
|
|
151
|
+
const direction = parts[1];
|
|
152
|
+
const itemIdx = parseInt(parts[2], 10);
|
|
153
|
+
const item = items[itemIdx];
|
|
154
|
+
if (item && item.value && item.value.startsWith('provider:')) {
|
|
155
|
+
const provIdx = parseInt(item.value.split(':')[1], 10);
|
|
156
|
+
const target = direction === 'down' ? provIdx + 1 : provIdx - 1;
|
|
157
|
+
if (target >= 0 && target < order.length) {
|
|
158
|
+
const temp = order[provIdx];
|
|
159
|
+
order[provIdx] = order[target];
|
|
160
|
+
order[target] = temp;
|
|
161
|
+
await saveProviderPreferences(order, enabled);
|
|
162
|
+
// Cursor follows the moved item
|
|
163
|
+
lastIndex = direction === 'down'
|
|
164
|
+
? Math.min(lastIndex + 1, items.length - 1)
|
|
165
|
+
: Math.max(lastIndex - 1, 0);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
printHeader();
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Select/activate provider
|
|
173
|
+
if (value.startsWith('provider:')) {
|
|
174
|
+
const provIdx = parseInt(value.split(':')[1], 10);
|
|
175
|
+
return order[provIdx];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
module.exports = { showProviderManagerMenu };
|