vibecodingmachine-cli 1.0.0
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/.allnightai/REQUIREMENTS.md +11 -0
- package/.allnightai/temp/auto-status.json +6 -0
- package/.env +7 -0
- package/.eslintrc.js +16 -0
- package/README.md +85 -0
- package/bin/vibecodingmachine.js +274 -0
- package/jest.config.js +8 -0
- package/logs/audit/2025-11-07.jsonl +2 -0
- package/package.json +64 -0
- package/scripts/README.md +128 -0
- package/scripts/auto-start-wrapper.sh +92 -0
- package/scripts/postinstall.js +81 -0
- package/src/commands/auth.js +96 -0
- package/src/commands/auto-direct.js +1748 -0
- package/src/commands/auto.js +4692 -0
- package/src/commands/auto.js.bak +710 -0
- package/src/commands/ide.js +70 -0
- package/src/commands/repo.js +159 -0
- package/src/commands/requirements.js +161 -0
- package/src/commands/setup.js +91 -0
- package/src/commands/status.js +88 -0
- package/src/components/RequirementPage.js +0 -0
- package/src/file.js +0 -0
- package/src/index.js +5 -0
- package/src/main.js +0 -0
- package/src/ui/requirements-page.js +0 -0
- package/src/utils/auth.js +548 -0
- package/src/utils/auto-mode-ansi-ui.js +238 -0
- package/src/utils/auto-mode-simple-ui.js +161 -0
- package/src/utils/auto-mode-ui.js.bak.blessed +207 -0
- package/src/utils/auto-mode.js +65 -0
- package/src/utils/config.js +64 -0
- package/src/utils/interactive.js +3616 -0
- package/src/utils/keyboard-handler.js +152 -0
- package/src/utils/logger.js +4 -0
- package/src/utils/persistent-header.js +116 -0
- package/src/utils/provider-registry.js +128 -0
- package/src/utils/requirementUtils.js +0 -0
- package/src/utils/status-card.js +120 -0
- package/src/utils/status-manager.js +0 -0
- package/src/utils/status.js +0 -0
- package/src/utils/stdout-interceptor.js +127 -0
- package/tests/auto-mode.test.js +37 -0
- package/tests/config.test.js +34 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const ansiEscapes = require('ansi-escapes');
|
|
3
|
+
const stripAnsi = require('strip-ansi');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ANSI-based UI for Auto Mode
|
|
7
|
+
* Simple, reliable, no module system issues
|
|
8
|
+
*/
|
|
9
|
+
class AutoModeANSIUI {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.menuContent = options.menuContent || '';
|
|
12
|
+
this.onExit = options.onExit;
|
|
13
|
+
this.requirement = 'Loading...';
|
|
14
|
+
this.step = 'PREPARE';
|
|
15
|
+
this.chatCount = 0;
|
|
16
|
+
this.maxChats = null;
|
|
17
|
+
this.progress = 0;
|
|
18
|
+
this.outputLines = [];
|
|
19
|
+
this.maxOutputLines = 15;
|
|
20
|
+
|
|
21
|
+
// Hide cursor for cleaner output
|
|
22
|
+
process.stdout.write(ansiEscapes.cursorHide);
|
|
23
|
+
|
|
24
|
+
// Handle exit - just use SIGINT, don't try to capture keys
|
|
25
|
+
// (stdin might be used by child process like Aider)
|
|
26
|
+
this.handleExit = () => {
|
|
27
|
+
this.destroy();
|
|
28
|
+
if (this.onExit) {
|
|
29
|
+
this.onExit();
|
|
30
|
+
}
|
|
31
|
+
process.exit(0);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
process.on('SIGINT', this.handleExit);
|
|
35
|
+
process.on('SIGTERM', this.handleExit);
|
|
36
|
+
|
|
37
|
+
// Initial render
|
|
38
|
+
this.render();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
updateStatus(status) {
|
|
42
|
+
if (status.requirement !== undefined) this.requirement = status.requirement;
|
|
43
|
+
if (status.step !== undefined) this.step = status.step;
|
|
44
|
+
if (status.chatCount !== undefined) this.chatCount = status.chatCount;
|
|
45
|
+
if (status.maxChats !== undefined) this.maxChats = status.maxChats;
|
|
46
|
+
if (status.progress !== undefined) this.progress = status.progress;
|
|
47
|
+
this.render();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
appendOutput(line) {
|
|
51
|
+
// Safety: truncate very long lines to prevent memory issues
|
|
52
|
+
const maxLineLength = 500;
|
|
53
|
+
const safeLine = line.length > maxLineLength ? line.substring(0, maxLineLength) + '...' : line;
|
|
54
|
+
|
|
55
|
+
this.outputLines.push(safeLine);
|
|
56
|
+
// Keep only last N lines
|
|
57
|
+
if (this.outputLines.length > this.maxOutputLines) {
|
|
58
|
+
this.outputLines = this.outputLines.slice(-this.maxOutputLines);
|
|
59
|
+
}
|
|
60
|
+
this.render();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
clearOutput() {
|
|
64
|
+
this.outputLines = [];
|
|
65
|
+
this.render();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
render() {
|
|
69
|
+
// Clear screen and move cursor to top
|
|
70
|
+
process.stdout.write(ansiEscapes.clearScreen);
|
|
71
|
+
process.stdout.write(ansiEscapes.cursorTo(0, 0));
|
|
72
|
+
|
|
73
|
+
let output = '';
|
|
74
|
+
|
|
75
|
+
// Header box
|
|
76
|
+
output += this.drawBox(this.menuContent, { color: 'cyan', style: 'round' });
|
|
77
|
+
output += '\n';
|
|
78
|
+
|
|
79
|
+
// Status card
|
|
80
|
+
const statusContent = this.buildStatusContent();
|
|
81
|
+
output += this.drawBox(statusContent, { color: 'magenta', style: 'round', title: ' Auto Mode Status ' });
|
|
82
|
+
output += '\n';
|
|
83
|
+
|
|
84
|
+
// Output log
|
|
85
|
+
const outputContent = this.buildOutputContent();
|
|
86
|
+
output += this.drawBox(outputContent, { color: 'green', style: 'single', title: ' Aider Output ' });
|
|
87
|
+
|
|
88
|
+
process.stdout.write(output);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
buildStatusContent() {
|
|
92
|
+
const stepColors = {
|
|
93
|
+
'PREPARE': chalk.cyan,
|
|
94
|
+
'ACT': chalk.yellow,
|
|
95
|
+
'CLEAN UP': chalk.magenta,
|
|
96
|
+
'VERIFY': chalk.blue,
|
|
97
|
+
'DONE': chalk.green,
|
|
98
|
+
'UNKNOWN': chalk.gray
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const stepColor = stepColors[this.step] || chalk.gray;
|
|
102
|
+
|
|
103
|
+
// Progress bar
|
|
104
|
+
const barWidth = 40;
|
|
105
|
+
const filledWidth = Math.round((this.progress / 100) * barWidth);
|
|
106
|
+
const emptyWidth = barWidth - filledWidth;
|
|
107
|
+
const progressBar = chalk.green('█'.repeat(filledWidth)) + chalk.gray('░'.repeat(emptyWidth));
|
|
108
|
+
|
|
109
|
+
// Chat counter
|
|
110
|
+
const chatDisplay = this.maxChats
|
|
111
|
+
? `Chat ${this.chatCount}/${this.maxChats}`
|
|
112
|
+
: `Chat ${this.chatCount} (unlimited)`;
|
|
113
|
+
|
|
114
|
+
// Truncate requirement if too long
|
|
115
|
+
const displayReq = this.requirement.length > 70
|
|
116
|
+
? this.requirement.substring(0, 67) + '...'
|
|
117
|
+
: this.requirement;
|
|
118
|
+
|
|
119
|
+
return `
|
|
120
|
+
${chalk.bold('📋 Current Requirement')}
|
|
121
|
+
|
|
122
|
+
${displayReq}
|
|
123
|
+
|
|
124
|
+
${chalk.bold('🚦 Status:')} ${stepColor.bold(this.step)}
|
|
125
|
+
|
|
126
|
+
${progressBar} ${this.progress}%
|
|
127
|
+
|
|
128
|
+
${chalk.gray(chatDisplay)}
|
|
129
|
+
`.trim();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
buildOutputContent() {
|
|
133
|
+
if (this.outputLines.length === 0) {
|
|
134
|
+
return chalk.gray('No output yet...');
|
|
135
|
+
}
|
|
136
|
+
return this.outputLines.join('\n');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
drawBox(content, options = {}) {
|
|
140
|
+
const { color = 'white', style = 'single', title = '' } = options;
|
|
141
|
+
const colorFn = chalk[color] || chalk.white;
|
|
142
|
+
|
|
143
|
+
// Box drawing characters
|
|
144
|
+
const chars = {
|
|
145
|
+
single: {
|
|
146
|
+
topLeft: '┌',
|
|
147
|
+
topRight: '┐',
|
|
148
|
+
bottomLeft: '└',
|
|
149
|
+
bottomRight: '┘',
|
|
150
|
+
horizontal: '─',
|
|
151
|
+
vertical: '│'
|
|
152
|
+
},
|
|
153
|
+
round: {
|
|
154
|
+
topLeft: '╭',
|
|
155
|
+
topRight: '╮',
|
|
156
|
+
bottomLeft: '╰',
|
|
157
|
+
bottomRight: '╯',
|
|
158
|
+
horizontal: '─',
|
|
159
|
+
vertical: '│'
|
|
160
|
+
},
|
|
161
|
+
double: {
|
|
162
|
+
topLeft: '╔',
|
|
163
|
+
topRight: '╗',
|
|
164
|
+
bottomLeft: '╚',
|
|
165
|
+
bottomRight: '╝',
|
|
166
|
+
horizontal: '═',
|
|
167
|
+
vertical: '║'
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const boxChars = chars[style] || chars.single;
|
|
172
|
+
const lines = content.split('\n');
|
|
173
|
+
|
|
174
|
+
// Use fixed width to avoid any width calculation issues
|
|
175
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
176
|
+
const width = Math.min(terminalWidth - 4, 60);
|
|
177
|
+
|
|
178
|
+
// Top border
|
|
179
|
+
let box = colorFn(boxChars.topLeft);
|
|
180
|
+
if (title) {
|
|
181
|
+
const titleLen = title.length;
|
|
182
|
+
const leftPad = Math.floor((width - titleLen - 2) / 2);
|
|
183
|
+
box += colorFn(boxChars.horizontal.repeat(leftPad));
|
|
184
|
+
box += colorFn(title);
|
|
185
|
+
box += colorFn(boxChars.horizontal.repeat(width - leftPad - titleLen));
|
|
186
|
+
} else {
|
|
187
|
+
box += colorFn(boxChars.horizontal.repeat(width));
|
|
188
|
+
}
|
|
189
|
+
box += colorFn(boxChars.topRight) + '\n';
|
|
190
|
+
|
|
191
|
+
// Content lines - use fixed padding to avoid width calculations
|
|
192
|
+
lines.forEach(line => {
|
|
193
|
+
// Truncate long lines
|
|
194
|
+
const maxLen = width - 4;
|
|
195
|
+
const displayLine = line.length > maxLen ? line.substring(0, maxLen - 3) + '...' : line;
|
|
196
|
+
const padding = Math.max(0, width - displayLine.length - 2);
|
|
197
|
+
box += colorFn(boxChars.vertical) + ' ' + displayLine + ' '.repeat(padding) + ' ' + colorFn(boxChars.vertical) + '\n';
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Bottom border
|
|
201
|
+
box += colorFn(boxChars.bottomLeft) + colorFn(boxChars.horizontal.repeat(width)) + colorFn(boxChars.bottomRight);
|
|
202
|
+
|
|
203
|
+
return box;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
getStringWidth(str) {
|
|
207
|
+
// Simple character count - don't try to strip ANSI
|
|
208
|
+
// This avoids any regex issues entirely
|
|
209
|
+
return str.length;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
destroy() {
|
|
213
|
+
// Clean up listeners
|
|
214
|
+
if (this.handleExit) {
|
|
215
|
+
process.removeListener('SIGINT', this.handleExit);
|
|
216
|
+
process.removeListener('SIGTERM', this.handleExit);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Show cursor again
|
|
220
|
+
process.stdout.write(ansiEscapes.cursorShow);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Create and display an ANSI-based UI for Auto Mode
|
|
226
|
+
* @param {object} options - UI configuration
|
|
227
|
+
* @param {string} options.menuContent - Menu content to display in header
|
|
228
|
+
* @param {function} options.onExit - Callback when user presses Ctrl+C
|
|
229
|
+
* @returns {AutoModeANSIUI} UI instance
|
|
230
|
+
*/
|
|
231
|
+
function createAutoModeUI(options = {}) {
|
|
232
|
+
return new AutoModeANSIUI(options);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
module.exports = {
|
|
236
|
+
createAutoModeUI,
|
|
237
|
+
AutoModeANSIUI
|
|
238
|
+
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const boxen = require('boxen');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Simple status-line UI for Auto Mode with persistent workflow card
|
|
6
|
+
* Shows a purple card similar to Electron app that stays visible
|
|
7
|
+
*/
|
|
8
|
+
class AutoModeSimpleUI {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.menuContent = options.menuContent || '';
|
|
11
|
+
this.requirement = 'Loading...';
|
|
12
|
+
this.step = 'PREPARE';
|
|
13
|
+
this.chatCount = 0;
|
|
14
|
+
this.maxChats = null;
|
|
15
|
+
this.progress = 0;
|
|
16
|
+
this.outputLineCount = 0; // Track output lines for periodic re-render
|
|
17
|
+
this.lastCardPrintTime = Date.now(); // Track when we last printed the card
|
|
18
|
+
this.lastCardPrintLine = 0; // Track line count when we last printed the card
|
|
19
|
+
|
|
20
|
+
// Print header once
|
|
21
|
+
console.log('\n' + chalk.bold.cyan('═══════════════════════════════════════════════════════════'));
|
|
22
|
+
console.log(this.menuContent);
|
|
23
|
+
console.log(chalk.bold.cyan('═══════════════════════════════════════════════════════════') + '\n');
|
|
24
|
+
|
|
25
|
+
// Initial workflow card
|
|
26
|
+
this.printWorkflowCard();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
updateStatus(status) {
|
|
30
|
+
if (status.requirement !== undefined) this.requirement = status.requirement;
|
|
31
|
+
if (status.step !== undefined) this.step = status.step;
|
|
32
|
+
if (status.chatCount !== undefined) this.chatCount = status.chatCount;
|
|
33
|
+
if (status.maxChats !== undefined) this.maxChats = status.maxChats;
|
|
34
|
+
if (status.progress !== undefined) this.progress = status.progress;
|
|
35
|
+
|
|
36
|
+
this.printWorkflowCard();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get icon for each workflow stage based on current step
|
|
41
|
+
*/
|
|
42
|
+
getStepIcon(stage, currentStage) {
|
|
43
|
+
const stageOrder = ['PREPARE', 'ACT', 'CLEAN UP', 'VERIFY', 'DONE'];
|
|
44
|
+
const currentIndex = stageOrder.indexOf(currentStage);
|
|
45
|
+
const stepIndex = stageOrder.indexOf(stage);
|
|
46
|
+
|
|
47
|
+
if (stepIndex < currentIndex) {
|
|
48
|
+
return '✅'; // Completed steps
|
|
49
|
+
} else if (stepIndex === currentIndex) {
|
|
50
|
+
if (currentStage === 'DONE') {
|
|
51
|
+
return '✅'; // Done is always checked
|
|
52
|
+
} else {
|
|
53
|
+
return '🔨'; // Current step
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
return '⏳'; // Pending steps
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Render the persistent workflow card (similar to Electron app's purple card)
|
|
62
|
+
*/
|
|
63
|
+
printWorkflowCard() {
|
|
64
|
+
// Step color mapping
|
|
65
|
+
const stepColors = {
|
|
66
|
+
'PREPARE': chalk.cyan,
|
|
67
|
+
'ACT': chalk.yellow,
|
|
68
|
+
'CLEAN UP': chalk.magenta,
|
|
69
|
+
'VERIFY': chalk.blue,
|
|
70
|
+
'DONE': chalk.green,
|
|
71
|
+
'UNKNOWN': chalk.gray
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const currentStepColor = stepColors[this.step] || chalk.gray;
|
|
75
|
+
|
|
76
|
+
// Build workflow states line: PREPARE ⏳ ACT ⏳ CLEAN UP ⏳ VERIFY ⏳ DONE
|
|
77
|
+
const stages = [
|
|
78
|
+
{ name: 'PREPARE', color: chalk.cyan },
|
|
79
|
+
{ name: 'ACT', color: chalk.yellow },
|
|
80
|
+
{ name: 'CLEAN UP', color: chalk.magenta },
|
|
81
|
+
{ name: 'VERIFY', color: chalk.blue },
|
|
82
|
+
{ name: 'DONE', color: chalk.green }
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
const workflowLine = stages.map((stage, index) => {
|
|
86
|
+
const icon = this.getStepIcon(stage.name, this.step);
|
|
87
|
+
const isCurrent = stage.name === this.step;
|
|
88
|
+
const stageColor = isCurrent ? currentStepColor.bold : stage.color;
|
|
89
|
+
return `${icon} ${stageColor(stage.name)}`;
|
|
90
|
+
}).join(` ${chalk.gray('⏳')} `);
|
|
91
|
+
|
|
92
|
+
// Truncate requirement if too long
|
|
93
|
+
const displayReq = this.requirement.length > 70
|
|
94
|
+
? this.requirement.substring(0, 67) + '...'
|
|
95
|
+
: this.requirement;
|
|
96
|
+
|
|
97
|
+
// Build card content
|
|
98
|
+
const cardContent = [
|
|
99
|
+
workflowLine,
|
|
100
|
+
'',
|
|
101
|
+
chalk.bold.white(`🎯 Working on: ${displayReq}`)
|
|
102
|
+
].join('\n');
|
|
103
|
+
|
|
104
|
+
// Render card with purple/magenta border (matching Electron app)
|
|
105
|
+
const card = boxen(cardContent, {
|
|
106
|
+
padding: { left: 1, right: 1, top: 1, bottom: 1 },
|
|
107
|
+
margin: { top: 0, right: 0, bottom: 1, left: 0 },
|
|
108
|
+
borderStyle: 'round',
|
|
109
|
+
borderColor: 'magenta',
|
|
110
|
+
backgroundColor: 'black'
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Print the card with a separator line before it for visibility
|
|
114
|
+
if (this.outputLineCount > 0) {
|
|
115
|
+
// Add separator when re-printing after output
|
|
116
|
+
console.log(chalk.gray('─'.repeat(80)));
|
|
117
|
+
}
|
|
118
|
+
console.log(card);
|
|
119
|
+
this.lastCardPrintTime = Date.now();
|
|
120
|
+
this.lastCardPrintLine = this.outputLineCount;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
appendOutput(line) {
|
|
124
|
+
// Periodically re-print the workflow card to keep it visible
|
|
125
|
+
// Re-print every 20 lines of output, or if more than 5 seconds have passed
|
|
126
|
+
const linesSinceLastCard = this.outputLineCount - this.lastCardPrintLine;
|
|
127
|
+
const timeSinceLastCard = Date.now() - this.lastCardPrintTime;
|
|
128
|
+
|
|
129
|
+
if (linesSinceLastCard >= 20 || timeSinceLastCard > 5000) {
|
|
130
|
+
this.printWorkflowCard();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Increment output line counter
|
|
134
|
+
if (line && line.trim()) {
|
|
135
|
+
this.outputLineCount++;
|
|
136
|
+
console.log(line);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
clearOutput() {
|
|
141
|
+
// No-op for simple UI
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
destroy() {
|
|
145
|
+
console.log('\n' + chalk.bold.green('Auto mode exited.') + '\n');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Create and display a simple status UI for Auto Mode
|
|
151
|
+
* @param {object} options - UI configuration
|
|
152
|
+
* @returns {AutoModeSimpleUI} UI instance
|
|
153
|
+
*/
|
|
154
|
+
function createAutoModeUI(options = {}) {
|
|
155
|
+
return new AutoModeSimpleUI(options);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
createAutoModeUI,
|
|
160
|
+
AutoModeSimpleUI
|
|
161
|
+
};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
const blessed = require('blessed');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create and display a blessed-based UI for Auto Mode
|
|
6
|
+
* Shows: persistent menu header, status card, and scrolling output
|
|
7
|
+
* @param {object} options - UI configuration
|
|
8
|
+
* @param {string} options.menuContent - Menu content to display in header
|
|
9
|
+
* @param {function} options.onExit - Callback when user presses Ctrl+C or q
|
|
10
|
+
* @returns {object} UI interface with methods to update status and append output
|
|
11
|
+
*/
|
|
12
|
+
function createAutoModeUI(options = {}) {
|
|
13
|
+
const { menuContent = '', onExit } = options;
|
|
14
|
+
|
|
15
|
+
// Create screen
|
|
16
|
+
const screen = blessed.screen({
|
|
17
|
+
smartCSR: true,
|
|
18
|
+
title: 'AllNightAI Auto Mode',
|
|
19
|
+
fullUnicode: true,
|
|
20
|
+
forceUnicode: true
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Clear screen and render immediately
|
|
24
|
+
screen.clearRegion(0, screen.width, 0, screen.height);
|
|
25
|
+
|
|
26
|
+
// Header box (menu) - use fixed height instead of shrink
|
|
27
|
+
const headerHeight = 11; // Fixed height for header
|
|
28
|
+
const header = blessed.box({
|
|
29
|
+
top: 0,
|
|
30
|
+
left: 0,
|
|
31
|
+
width: '100%',
|
|
32
|
+
height: headerHeight,
|
|
33
|
+
content: menuContent,
|
|
34
|
+
tags: true,
|
|
35
|
+
border: {
|
|
36
|
+
type: 'line'
|
|
37
|
+
},
|
|
38
|
+
style: {
|
|
39
|
+
border: {
|
|
40
|
+
fg: 'cyan'
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Status card (purple/magenta border like GUI)
|
|
46
|
+
const statusCardHeight = 12;
|
|
47
|
+
const statusCard = blessed.box({
|
|
48
|
+
top: headerHeight,
|
|
49
|
+
left: 0,
|
|
50
|
+
width: '100%',
|
|
51
|
+
height: statusCardHeight,
|
|
52
|
+
tags: true,
|
|
53
|
+
border: {
|
|
54
|
+
type: 'round'
|
|
55
|
+
},
|
|
56
|
+
style: {
|
|
57
|
+
border: {
|
|
58
|
+
fg: 'magenta'
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
label: ' Auto Mode Status '
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Output log (scrollable)
|
|
65
|
+
const totalHeaderHeight = headerHeight + statusCardHeight;
|
|
66
|
+
const outputLog = blessed.log({
|
|
67
|
+
top: totalHeaderHeight,
|
|
68
|
+
left: 0,
|
|
69
|
+
width: '100%',
|
|
70
|
+
height: `100%-${totalHeaderHeight}`,
|
|
71
|
+
tags: true,
|
|
72
|
+
border: {
|
|
73
|
+
type: 'line'
|
|
74
|
+
},
|
|
75
|
+
style: {
|
|
76
|
+
border: {
|
|
77
|
+
fg: 'green'
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
label: ' Aider Output ',
|
|
81
|
+
scrollable: true,
|
|
82
|
+
alwaysScroll: true,
|
|
83
|
+
mouse: true,
|
|
84
|
+
keys: true,
|
|
85
|
+
vi: true,
|
|
86
|
+
scrollbar: {
|
|
87
|
+
ch: ' ',
|
|
88
|
+
track: {
|
|
89
|
+
bg: 'cyan'
|
|
90
|
+
},
|
|
91
|
+
style: {
|
|
92
|
+
inverse: true
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Append all elements to screen
|
|
98
|
+
screen.append(header);
|
|
99
|
+
screen.append(statusCard);
|
|
100
|
+
screen.append(outputLog);
|
|
101
|
+
|
|
102
|
+
// Focus the output log for scrolling
|
|
103
|
+
outputLog.focus();
|
|
104
|
+
|
|
105
|
+
// Key bindings
|
|
106
|
+
screen.key(['escape', 'q', 'C-c'], () => {
|
|
107
|
+
if (onExit) {
|
|
108
|
+
onExit();
|
|
109
|
+
}
|
|
110
|
+
screen.destroy();
|
|
111
|
+
process.exit(0);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Initial render
|
|
115
|
+
screen.render();
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Update the status card with new status information
|
|
119
|
+
* @param {object} status - Status object
|
|
120
|
+
* @param {string} status.requirement - Current requirement
|
|
121
|
+
* @param {string} status.step - Current step (PREPARE, ACT, CLEAN UP, VERIFY, DONE)
|
|
122
|
+
* @param {number} status.chatCount - Current chat count
|
|
123
|
+
* @param {number|null} status.maxChats - Max chats or null for unlimited
|
|
124
|
+
* @param {number} status.progress - Progress percentage (0-100)
|
|
125
|
+
*/
|
|
126
|
+
function updateStatus(status) {
|
|
127
|
+
const {
|
|
128
|
+
requirement = 'No requirement loaded',
|
|
129
|
+
step = 'UNKNOWN',
|
|
130
|
+
chatCount = 0,
|
|
131
|
+
maxChats = null,
|
|
132
|
+
progress = 0
|
|
133
|
+
} = status;
|
|
134
|
+
|
|
135
|
+
// Step color mapping
|
|
136
|
+
const stepColors = {
|
|
137
|
+
'PREPARE': '{cyan-fg}',
|
|
138
|
+
'ACT': '{yellow-fg}',
|
|
139
|
+
'CLEAN UP': '{magenta-fg}',
|
|
140
|
+
'VERIFY': '{blue-fg}',
|
|
141
|
+
'DONE': '{green-fg}',
|
|
142
|
+
'UNKNOWN': '{gray-fg}'
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const stepColor = stepColors[step] || '{gray-fg}';
|
|
146
|
+
|
|
147
|
+
// Progress bar
|
|
148
|
+
const barWidth = 40;
|
|
149
|
+
const filledWidth = Math.round((progress / 100) * barWidth);
|
|
150
|
+
const emptyWidth = barWidth - filledWidth;
|
|
151
|
+
const progressBar = '{green-fg}' + '█'.repeat(filledWidth) + '{/green-fg}' +
|
|
152
|
+
'{gray-fg}' + '░'.repeat(emptyWidth) + '{/gray-fg}';
|
|
153
|
+
|
|
154
|
+
// Chat counter
|
|
155
|
+
const chatDisplay = maxChats
|
|
156
|
+
? `Chat ${chatCount}/${maxChats}`
|
|
157
|
+
: `Chat ${chatCount} (unlimited)`;
|
|
158
|
+
|
|
159
|
+
// Build card content
|
|
160
|
+
const content = `
|
|
161
|
+
{bold}📋 Current Requirement{/bold}
|
|
162
|
+
|
|
163
|
+
${requirement.length > 70 ? requirement.substring(0, 67) + '...' : requirement}
|
|
164
|
+
|
|
165
|
+
{bold}🚦 Status:{/bold} ${stepColor}{bold}${step}{/bold}{/}
|
|
166
|
+
|
|
167
|
+
${progressBar} ${progress}%
|
|
168
|
+
|
|
169
|
+
{gray-fg}${chatDisplay}{/gray-fg}
|
|
170
|
+
`;
|
|
171
|
+
|
|
172
|
+
statusCard.setContent(content);
|
|
173
|
+
screen.render();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Append a line to the output log
|
|
178
|
+
* @param {string} line - Line to append
|
|
179
|
+
*/
|
|
180
|
+
function appendOutput(line) {
|
|
181
|
+
outputLog.log(line);
|
|
182
|
+
screen.render();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Clear the output log
|
|
187
|
+
*/
|
|
188
|
+
function clearOutput() {
|
|
189
|
+
outputLog.setContent('');
|
|
190
|
+
screen.render();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
screen,
|
|
195
|
+
header,
|
|
196
|
+
statusCard,
|
|
197
|
+
outputLog,
|
|
198
|
+
updateStatus,
|
|
199
|
+
appendOutput,
|
|
200
|
+
clearOutput,
|
|
201
|
+
destroy: () => screen.destroy()
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
module.exports = {
|
|
206
|
+
createAutoModeUI
|
|
207
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const { getRepoPath } = require('./config');
|
|
4
|
+
const { logAutoModeStart, logAutoModeStop } = require('@vibecodingmachine/core');
|
|
5
|
+
|
|
6
|
+
function getStatusPath(repoPath) {
|
|
7
|
+
return path.join(repoPath, '.vibecodingmachine', 'temp', 'auto-status.json');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function checkAutoModeStatus() {
|
|
11
|
+
const repoPath = await getRepoPath();
|
|
12
|
+
if (!repoPath) return { running: false };
|
|
13
|
+
const statusPath = getStatusPath(repoPath);
|
|
14
|
+
if (!await fs.pathExists(statusPath)) return { running: false };
|
|
15
|
+
try {
|
|
16
|
+
const status = await fs.readJson(statusPath);
|
|
17
|
+
return status;
|
|
18
|
+
} catch {
|
|
19
|
+
return { running: false };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function startAutoMode(repoPath, config) {
|
|
24
|
+
const statusPath = getStatusPath(repoPath);
|
|
25
|
+
await fs.ensureDir(path.dirname(statusPath));
|
|
26
|
+
const status = {
|
|
27
|
+
running: true,
|
|
28
|
+
startedAt: new Date().toISOString(),
|
|
29
|
+
chatCount: 0,
|
|
30
|
+
ide: config.ide || 'cline'
|
|
31
|
+
};
|
|
32
|
+
await fs.writeJson(statusPath, status, { spaces: 2 });
|
|
33
|
+
|
|
34
|
+
// Log to audit log
|
|
35
|
+
logAutoModeStart(config.ide || 'cline', config.maxChats || 0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function stopAutoMode(reason = 'manual') {
|
|
39
|
+
const repoPath = await getRepoPath();
|
|
40
|
+
if (!repoPath) return;
|
|
41
|
+
const statusPath = getStatusPath(repoPath);
|
|
42
|
+
if (!await fs.pathExists(statusPath)) return;
|
|
43
|
+
const current = await fs.readJson(statusPath).catch(() => ({}));
|
|
44
|
+
await fs.writeJson(statusPath, { ...current, running: false }, { spaces: 2 });
|
|
45
|
+
|
|
46
|
+
// Log to audit log
|
|
47
|
+
logAutoModeStop(reason);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function updateAutoModeStatus(repoPath, updates) {
|
|
51
|
+
const statusPath = getStatusPath(repoPath);
|
|
52
|
+
if (!await fs.pathExists(statusPath)) return;
|
|
53
|
+
const current = await fs.readJson(statusPath).catch(() => ({ running: true }));
|
|
54
|
+
await fs.writeJson(statusPath, { ...current, ...updates }, { spaces: 2 });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
checkAutoModeStatus,
|
|
59
|
+
startAutoMode,
|
|
60
|
+
stopAutoMode,
|
|
61
|
+
updateAutoModeStatus
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|