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,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TRUI Quick Menu — raw-stdin keypress navigation engine
|
|
3
|
+
*
|
|
4
|
+
* Provides showQuickMenu(items, initialIndex) which renders a menu and listens
|
|
5
|
+
* for keypresses directly on stdin (no inquirer dependency).
|
|
6
|
+
*
|
|
7
|
+
* Item types:
|
|
8
|
+
* 'setting' — gray text; gets a letter shortcut; selectable
|
|
9
|
+
* 'action' — white text; gets a letter shortcut; selectable
|
|
10
|
+
* 'info' — gray text; NO letter; NOT selectable (display only)
|
|
11
|
+
* 'blank' — empty separator line; not selectable
|
|
12
|
+
*
|
|
13
|
+
* Returns: Promise<{ value: string, selectedIndex: number }>
|
|
14
|
+
* Special value '__cancel__' is returned when ESC or left-arrow is pressed.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const chalk = require('chalk');
|
|
18
|
+
const readline = require('readline');
|
|
19
|
+
const { debugLogger, perfMonitor, stateTracker, errorHandler } = require('./trui-debug');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Convert a zero-based index to a letter shortcut (0→'a', 1→'b', …)
|
|
23
|
+
* @param {number} index
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
function indexToLetter(index) {
|
|
27
|
+
return String.fromCharCode(97 + index);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Strip ANSI escape codes from a string for length calculation
|
|
32
|
+
*/
|
|
33
|
+
function stripAnsi(str) {
|
|
34
|
+
return str.replace(
|
|
35
|
+
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
|
36
|
+
''
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Calculate the number of terminal lines a piece of text occupies
|
|
42
|
+
* (accounts for line wrapping based on terminal column width)
|
|
43
|
+
*/
|
|
44
|
+
function getVisualLineCount(text) {
|
|
45
|
+
const columns = process.stdout.columns || 80;
|
|
46
|
+
let lineCount = 0;
|
|
47
|
+
for (const line of text.split('\n')) {
|
|
48
|
+
const len = stripAnsi(line).length;
|
|
49
|
+
lineCount += len === 0 ? 1 : Math.ceil(len / columns);
|
|
50
|
+
}
|
|
51
|
+
return lineCount;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Determine whether an item can receive keyboard focus
|
|
56
|
+
*/
|
|
57
|
+
function isSelectable(item) {
|
|
58
|
+
return item.type !== 'blank' && item.type !== 'info';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Build and display the menu; return the total number of visual lines printed
|
|
63
|
+
* so subsequent renders can reposition the cursor without clearing.
|
|
64
|
+
*
|
|
65
|
+
* @param {Array} items
|
|
66
|
+
* @param {number} selectedIndex
|
|
67
|
+
* @param {string} [hintText] - override the bottom hint line
|
|
68
|
+
* @returns {number} lines printed
|
|
69
|
+
*/
|
|
70
|
+
function renderMenu(items, selectedIndex, hintText) {
|
|
71
|
+
let linesPrinted = 0;
|
|
72
|
+
let letterIndex = 0;
|
|
73
|
+
|
|
74
|
+
items.forEach((item, index) => {
|
|
75
|
+
const isSelected = index === selectedIndex;
|
|
76
|
+
let output = '';
|
|
77
|
+
|
|
78
|
+
if (item.type === 'blank') {
|
|
79
|
+
process.stdout.write('\n');
|
|
80
|
+
linesPrinted += 1;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (item.type === 'info') {
|
|
85
|
+
output = chalk.gray(` ${item.name}`);
|
|
86
|
+
process.stdout.write(output + '\n');
|
|
87
|
+
linesPrinted += getVisualLineCount(` ${item.name}`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 'header' type: no letter, but IS selectable (focusable with ↑↓, Enter returns value)
|
|
92
|
+
if (item.type === 'header') {
|
|
93
|
+
const raw = `${isSelected ? '❯ ' : ' '}${item.name}`;
|
|
94
|
+
if (isSelected) {
|
|
95
|
+
output = chalk.cyan(`❯ ${item.name}`);
|
|
96
|
+
} else {
|
|
97
|
+
output = chalk.gray(` ${item.name}`);
|
|
98
|
+
}
|
|
99
|
+
process.stdout.write(output + '\n');
|
|
100
|
+
linesPrinted += getVisualLineCount(raw);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Assign letter — 'x' always = exit; everything else gets a→z in order
|
|
105
|
+
let letter;
|
|
106
|
+
if (item.value === 'exit') {
|
|
107
|
+
letter = 'x';
|
|
108
|
+
} else {
|
|
109
|
+
letter = indexToLetter(letterIndex);
|
|
110
|
+
letterIndex++;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const prefix = isSelected ? chalk.cyan('❯ ') : ' ';
|
|
114
|
+
const raw = `${isSelected ? '❯ ' : ' '}${letter}) ${item.name}`;
|
|
115
|
+
|
|
116
|
+
if (isSelected) {
|
|
117
|
+
output = chalk.cyan(`❯ ${letter}) ${item.name}`);
|
|
118
|
+
} else if (item.type === 'setting') {
|
|
119
|
+
output = chalk.gray(` ${letter}) ${item.name}`);
|
|
120
|
+
} else {
|
|
121
|
+
output = ` ${letter}) ${item.name}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
process.stdout.write(output + '\n');
|
|
125
|
+
linesPrinted += getVisualLineCount(raw);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Help hint at the bottom
|
|
129
|
+
let helpText;
|
|
130
|
+
if (hintText !== undefined) {
|
|
131
|
+
helpText = `\n ${hintText}`;
|
|
132
|
+
} else {
|
|
133
|
+
const letterCount = items.filter(
|
|
134
|
+
i => i.type !== 'blank' && i.type !== 'info' && i.value !== 'exit'
|
|
135
|
+
).length;
|
|
136
|
+
const lastLetter = letterCount > 0 ? indexToLetter(letterCount - 1) : 'a';
|
|
137
|
+
const rangeText = letterCount > 1 ? `a-${lastLetter}` : letterCount === 1 ? 'a' : '';
|
|
138
|
+
helpText = `\n [${rangeText ? rangeText + ' ' : ''}↑↓ navigate ← back]`;
|
|
139
|
+
}
|
|
140
|
+
process.stdout.write(chalk.gray(helpText) + '\n');
|
|
141
|
+
linesPrinted += getVisualLineCount(helpText);
|
|
142
|
+
|
|
143
|
+
return linesPrinted;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Debounce guard — shared between calls to prevent key-repeat re-entry
|
|
147
|
+
let menuSuppressUntil = 0;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Show a quick menu and wait for the user to select an item.
|
|
151
|
+
*
|
|
152
|
+
* @param {Array<{type: string, name: string, value: string}>} items
|
|
153
|
+
* @param {number} [initialSelectedIndex=0]
|
|
154
|
+
* @param {Object} [options={}]
|
|
155
|
+
* @param {Function} [options.extraKeys] - called for unhandled keys:
|
|
156
|
+
* extraKeys(str, key, selectedIndex, { display, setSelectedIndex, resolveWith, items })
|
|
157
|
+
* Return true to indicate the key was handled.
|
|
158
|
+
* @param {string} [options.hintText] - override the bottom hint line
|
|
159
|
+
* @returns {Promise<{value: string, selectedIndex: number}>}
|
|
160
|
+
*/
|
|
161
|
+
function showQuickMenu(items, initialSelectedIndex = 0, options = {}) {
|
|
162
|
+
const { extraKeys, hintText } = options;
|
|
163
|
+
try {
|
|
164
|
+
perfMonitor.start('showQuickMenu');
|
|
165
|
+
debugLogger.info('showQuickMenu called', { itemCount: items.length, initialSelectedIndex });
|
|
166
|
+
|
|
167
|
+
stateTracker.push({
|
|
168
|
+
type: 'menu',
|
|
169
|
+
items,
|
|
170
|
+
initialSelectedIndex,
|
|
171
|
+
startTime: Date.now()
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return new Promise(resolve => {
|
|
175
|
+
// Find first selectable item at or after initialSelectedIndex
|
|
176
|
+
let selectedIndex = initialSelectedIndex;
|
|
177
|
+
while (selectedIndex < items.length && !isSelectable(items[selectedIndex])) {
|
|
178
|
+
selectedIndex++;
|
|
179
|
+
}
|
|
180
|
+
if (selectedIndex >= items.length) {
|
|
181
|
+
selectedIndex = items.findIndex(isSelectable);
|
|
182
|
+
if (selectedIndex === -1) selectedIndex = 0;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
debugLogger.info('Initial selectedIndex set', { selectedIndex });
|
|
186
|
+
stateTracker.update({ selectedIndex });
|
|
187
|
+
|
|
188
|
+
let lastLinesPrinted = 0;
|
|
189
|
+
let isFirstRender = true;
|
|
190
|
+
|
|
191
|
+
const display = () => {
|
|
192
|
+
perfMonitor.start('render');
|
|
193
|
+
if (!isFirstRender) {
|
|
194
|
+
// Move cursor back up to start of menu and clear downward
|
|
195
|
+
readline.moveCursor(process.stdout, 0, -lastLinesPrinted);
|
|
196
|
+
readline.clearScreenDown(process.stdout);
|
|
197
|
+
}
|
|
198
|
+
lastLinesPrinted = renderMenu(items, selectedIndex, hintText);
|
|
199
|
+
isFirstRender = false;
|
|
200
|
+
perfMonitor.end('render');
|
|
201
|
+
debugLogger.info('Menu rendered', { linesPrinted: lastLinesPrinted, selectedIndex });
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const cleanup = () => {
|
|
205
|
+
debugLogger.info('Cleaning up menu listeners');
|
|
206
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
207
|
+
process.stdin.setRawMode(false);
|
|
208
|
+
}
|
|
209
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
210
|
+
process.stdin.pause();
|
|
211
|
+
perfMonitor.end('showQuickMenu');
|
|
212
|
+
stateTracker.pop();
|
|
213
|
+
debugLogger.info('Menu cleanup completed', perfMonitor.getMetrics());
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const selectOption = index => {
|
|
217
|
+
debugLogger.info('Option selected', { index, value: items[index].value, name: items[index].name });
|
|
218
|
+
cleanup();
|
|
219
|
+
// Clear the selection indicator line
|
|
220
|
+
readline.moveCursor(process.stdout, 0, -(lastLinesPrinted - (index)));
|
|
221
|
+
readline.clearScreenDown(process.stdout);
|
|
222
|
+
process.stdout.write(chalk.cyan(` → ${items[index].name}\n`));
|
|
223
|
+
menuSuppressUntil = Date.now() + 300;
|
|
224
|
+
resolve({ value: items[index].value, selectedIndex: index });
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const onKeypress = (str, key) => {
|
|
228
|
+
stateTracker.update({ lastKeypress: { str, key } });
|
|
229
|
+
debugLogger.info('Keypress received', { str, key: key ? { name: key.name, ctrl: key.ctrl, shift: key.shift } : null });
|
|
230
|
+
|
|
231
|
+
if (!key) return;
|
|
232
|
+
|
|
233
|
+
// Always allow Ctrl+C
|
|
234
|
+
if (key.ctrl && key.name === 'c') {
|
|
235
|
+
cleanup();
|
|
236
|
+
process.exit(0);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Debounce
|
|
241
|
+
if (Date.now() < menuSuppressUntil) return;
|
|
242
|
+
|
|
243
|
+
// ESC → always cancel immediately
|
|
244
|
+
if (key.name === 'escape') {
|
|
245
|
+
cleanup();
|
|
246
|
+
menuSuppressUntil = Date.now() + 300;
|
|
247
|
+
resolve({ value: '__cancel__', selectedIndex });
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Give caller first crack at keys (before letter shortcuts and left-arrow cancel)
|
|
252
|
+
if (extraKeys) {
|
|
253
|
+
const handled = extraKeys(str, key, selectedIndex, {
|
|
254
|
+
display,
|
|
255
|
+
setSelectedIndex: (i) => { selectedIndex = i; stateTracker.update({ selectedIndex }); },
|
|
256
|
+
resolveWith: (value) => { cleanup(); resolve({ value, selectedIndex }); },
|
|
257
|
+
items,
|
|
258
|
+
});
|
|
259
|
+
if (handled) return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Left-arrow → cancel (if extraKeys did not handle it)
|
|
263
|
+
if (key.name === 'left') {
|
|
264
|
+
cleanup();
|
|
265
|
+
menuSuppressUntil = Date.now() + 300;
|
|
266
|
+
resolve({ value: '__cancel__', selectedIndex });
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Letter shortcuts
|
|
271
|
+
if (str && str.length === 1 && str >= 'a' && str <= 'z') {
|
|
272
|
+
if (str === 'x') {
|
|
273
|
+
// Find exit item
|
|
274
|
+
const exitIdx = items.findIndex(i => i.value === 'exit');
|
|
275
|
+
if (exitIdx !== -1) {
|
|
276
|
+
selectOption(exitIdx);
|
|
277
|
+
} else {
|
|
278
|
+
cleanup();
|
|
279
|
+
resolve({ value: 'exit', selectedIndex });
|
|
280
|
+
}
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (str === 'f') {
|
|
285
|
+
// F key for feedback - global shortcut
|
|
286
|
+
cleanup();
|
|
287
|
+
resolve({ value: 'feedback', selectedIndex });
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const letterIdx = str.charCodeAt(0) - 97;
|
|
292
|
+
let count = 0;
|
|
293
|
+
for (let i = 0; i < items.length; i++) {
|
|
294
|
+
if (items[i].type !== 'blank' && items[i].type !== 'info' && items[i].value !== 'exit') {
|
|
295
|
+
if (count === letterIdx) {
|
|
296
|
+
selectOption(i);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
count++;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Arrow up
|
|
305
|
+
if (key.name === 'up') {
|
|
306
|
+
let next = selectedIndex - 1;
|
|
307
|
+
while (next >= 0 && !isSelectable(items[next])) next--;
|
|
308
|
+
if (next < 0) {
|
|
309
|
+
// Wrap to bottom
|
|
310
|
+
next = items.length - 1;
|
|
311
|
+
while (next >= 0 && !isSelectable(items[next])) next--;
|
|
312
|
+
}
|
|
313
|
+
if (next >= 0) {
|
|
314
|
+
selectedIndex = next;
|
|
315
|
+
stateTracker.update({ selectedIndex });
|
|
316
|
+
display();
|
|
317
|
+
}
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Arrow down
|
|
322
|
+
if (key.name === 'down') {
|
|
323
|
+
let next = selectedIndex + 1;
|
|
324
|
+
while (next < items.length && !isSelectable(items[next])) next++;
|
|
325
|
+
if (next >= items.length) {
|
|
326
|
+
// Wrap to top
|
|
327
|
+
next = 0;
|
|
328
|
+
while (next < items.length && !isSelectable(items[next])) next++;
|
|
329
|
+
}
|
|
330
|
+
if (next < items.length) {
|
|
331
|
+
selectedIndex = next;
|
|
332
|
+
stateTracker.update({ selectedIndex });
|
|
333
|
+
display();
|
|
334
|
+
}
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Enter or right-arrow → select
|
|
339
|
+
if (key.name === 'return' || key.name === 'right') {
|
|
340
|
+
if (isSelectable(items[selectedIndex])) {
|
|
341
|
+
selectOption(selectedIndex);
|
|
342
|
+
}
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// Initial render
|
|
348
|
+
display();
|
|
349
|
+
|
|
350
|
+
// Set up keypress listener
|
|
351
|
+
readline.emitKeypressEvents(process.stdin);
|
|
352
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
353
|
+
process.stdin.setRawMode(true);
|
|
354
|
+
}
|
|
355
|
+
menuSuppressUntil = Date.now() + 300;
|
|
356
|
+
process.stdin.on('keypress', onKeypress);
|
|
357
|
+
process.stdin.resume();
|
|
358
|
+
});
|
|
359
|
+
} catch (error) {
|
|
360
|
+
debugLogger.error('showQuickMenu error', { error: error.message, stack: error.stack });
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
module.exports = { showQuickMenu, indexToLetter, stripAnsi, getVisualLineCount };
|