vibecodingmachine-cli 2026.1.29-713 → 2026.2.20-426

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.
Files changed (46) hide show
  1. package/bin/vibecodingmachine.js +124 -2
  2. package/package.json +3 -2
  3. package/src/commands/agents-check.js +69 -0
  4. package/src/commands/auto-direct.js +930 -145
  5. package/src/commands/auto.js +32 -8
  6. package/src/commands/ide.js +2 -1
  7. package/src/commands/requirements.js +23 -27
  8. package/src/utils/auto-mode.js +4 -1
  9. package/src/utils/cline-js-handler.js +218 -0
  10. package/src/utils/config.js +22 -0
  11. package/src/utils/display-formatters-complete.js +229 -0
  12. package/src/utils/display-formatters-extracted.js +219 -0
  13. package/src/utils/display-formatters.js +157 -0
  14. package/src/utils/feedback-handler.js +143 -0
  15. package/src/utils/first-run.js +5 -8
  16. package/src/utils/ide-detection-complete.js +126 -0
  17. package/src/utils/ide-detection-extracted.js +116 -0
  18. package/src/utils/ide-detection.js +124 -0
  19. package/src/utils/interactive-backup.js +5664 -0
  20. package/src/utils/interactive-broken.js +280 -0
  21. package/src/utils/interactive.js +357 -2367
  22. package/src/utils/provider-checker.js +410 -0
  23. package/src/utils/provider-manager.js +254 -0
  24. package/src/utils/provider-registry.js +18 -9
  25. package/src/utils/requirement-actions.js +884 -0
  26. package/src/utils/requirements-navigator.js +587 -0
  27. package/src/utils/rui-trui-adapter.js +311 -0
  28. package/src/utils/simple-trui.js +204 -0
  29. package/src/utils/status-helpers-extracted.js +125 -0
  30. package/src/utils/status-helpers.js +107 -0
  31. package/src/utils/trui-debug.js +261 -0
  32. package/src/utils/trui-feedback.js +133 -0
  33. package/src/utils/trui-nav-agents.js +119 -0
  34. package/src/utils/trui-nav-requirements.js +268 -0
  35. package/src/utils/trui-nav-settings.js +157 -0
  36. package/src/utils/trui-nav-specifications.js +139 -0
  37. package/src/utils/trui-navigation.js +305 -0
  38. package/src/utils/trui-provider-manager.js +182 -0
  39. package/src/utils/trui-quick-menu.js +370 -0
  40. package/src/utils/trui-req-actions.js +372 -0
  41. package/src/utils/trui-req-tree.js +534 -0
  42. package/src/utils/trui-specifications.js +359 -0
  43. package/src/utils/trui-text-editor.js +350 -0
  44. package/src/utils/trui-windsurf.js +350 -0
  45. package/src/utils/welcome-screen-extracted.js +135 -0
  46. package/src/utils/welcome-screen.js +134 -0
@@ -0,0 +1,370 @@
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
+ // Reset debounce timer when cleaning up
215
+ menuSuppressUntil = 0;
216
+ };
217
+
218
+ const selectOption = index => {
219
+ debugLogger.info('Option selected', { index, value: items[index].value, name: items[index].name });
220
+ cleanup();
221
+ // Clear the selection indicator line
222
+ readline.moveCursor(process.stdout, 0, -(lastLinesPrinted - (index)));
223
+ readline.clearScreenDown(process.stdout);
224
+ process.stdout.write(chalk.cyan(` → ${items[index].name}\n`));
225
+ menuSuppressUntil = Date.now() + 300;
226
+ resolve({ value: items[index].value, selectedIndex: index });
227
+ };
228
+
229
+ const onKeypress = (str, key) => {
230
+ stateTracker.update({ lastKeypress: { str, key } });
231
+ debugLogger.info('Keypress received', { str, key: key ? { name: key.name, ctrl: key.ctrl, shift: key.shift } : null });
232
+
233
+ if (!key) return;
234
+
235
+ // Always allow Ctrl+C
236
+ if (key.ctrl && key.name === 'c') {
237
+ cleanup();
238
+ process.exit(0);
239
+ return;
240
+ }
241
+
242
+ // Debounce
243
+ if (Date.now() < menuSuppressUntil) return;
244
+
245
+ // ESC → always cancel immediately
246
+ if (key.name === 'escape') {
247
+ cleanup();
248
+ menuSuppressUntil = Date.now() + 300;
249
+ resolve({ value: '__cancel__', selectedIndex });
250
+ return;
251
+ }
252
+
253
+ // Give caller first crack at keys (before letter shortcuts and left-arrow cancel)
254
+ if (extraKeys) {
255
+ const handled = extraKeys(str, key, selectedIndex, {
256
+ display,
257
+ setSelectedIndex: (i) => { selectedIndex = i; stateTracker.update({ selectedIndex }); },
258
+ resolveWith: (value) => { cleanup(); resolve({ value, selectedIndex }); },
259
+ items,
260
+ });
261
+ if (handled) return;
262
+ }
263
+
264
+ // Left-arrow → cancel (if extraKeys did not handle it)
265
+ if (key.name === 'left') {
266
+ cleanup();
267
+ menuSuppressUntil = Date.now() + 300;
268
+ resolve({ value: '__cancel__', selectedIndex });
269
+ return;
270
+ }
271
+
272
+ // Letter shortcuts
273
+ if (str && str.length === 1 && str >= 'a' && str <= 'z') {
274
+ if (str === 'x') {
275
+ // Find exit item
276
+ const exitIdx = items.findIndex(i => i.value === 'exit');
277
+ if (exitIdx !== -1) {
278
+ selectOption(exitIdx);
279
+ } else {
280
+ cleanup();
281
+ resolve({ value: 'exit', selectedIndex });
282
+ }
283
+ return;
284
+ }
285
+
286
+ if (str === 'f') {
287
+ // F key for feedback - global shortcut
288
+ cleanup();
289
+ resolve({ value: 'feedback', selectedIndex });
290
+ return;
291
+ }
292
+
293
+ const letterIdx = str.charCodeAt(0) - 97;
294
+ let count = 0;
295
+ for (let i = 0; i < items.length; i++) {
296
+ if (items[i].type !== 'blank' && items[i].type !== 'info' && items[i].value !== 'exit') {
297
+ if (count === letterIdx) {
298
+ selectOption(i);
299
+ return;
300
+ }
301
+ count++;
302
+ }
303
+ }
304
+ // Invalid letter - don't do anything, just return
305
+ return;
306
+ }
307
+
308
+ // Arrow up
309
+ if (key.name === 'up') {
310
+ let next = selectedIndex - 1;
311
+ while (next >= 0 && !isSelectable(items[next])) next--;
312
+ if (next < 0) {
313
+ // Wrap to bottom
314
+ next = items.length - 1;
315
+ while (next >= 0 && !isSelectable(items[next])) next--;
316
+ }
317
+ if (next >= 0 && next < items.length && isSelectable(items[next])) {
318
+ selectedIndex = next;
319
+ stateTracker.update({ selectedIndex });
320
+ display();
321
+ }
322
+ return;
323
+ }
324
+
325
+ // Arrow down
326
+ if (key.name === 'down') {
327
+ let next = selectedIndex + 1;
328
+ while (next < items.length && !isSelectable(items[next])) next++;
329
+ if (next >= items.length) {
330
+ // Wrap to top
331
+ next = 0;
332
+ while (next < items.length && !isSelectable(items[next])) next++;
333
+ }
334
+ if (next >= 0 && next < items.length && isSelectable(items[next])) {
335
+ selectedIndex = next;
336
+ stateTracker.update({ selectedIndex });
337
+ display();
338
+ }
339
+ return;
340
+ }
341
+
342
+ // Enter or right-arrow → select
343
+ if (key.name === 'return' || key.name === 'right') {
344
+ if (isSelectable(items[selectedIndex])) {
345
+ selectOption(selectedIndex);
346
+ }
347
+ return;
348
+ }
349
+ };
350
+
351
+ // Initial render
352
+ display();
353
+
354
+ // Set up keypress listener
355
+ readline.emitKeypressEvents(process.stdin);
356
+ if (process.stdin.isTTY && process.stdin.setRawMode) {
357
+ process.stdin.setRawMode(true);
358
+ }
359
+ // Shorter debounce (50ms) to prevent accidental double-presses while still being responsive
360
+ menuSuppressUntil = Date.now() + 50;
361
+ process.stdin.on('keypress', onKeypress);
362
+ process.stdin.resume();
363
+ });
364
+ } catch (error) {
365
+ debugLogger.error('showQuickMenu error', { error: error.message, stack: error.stack });
366
+ throw error;
367
+ }
368
+ }
369
+
370
+ module.exports = { showQuickMenu, indexToLetter, stripAnsi, getVisualLineCount };