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,152 @@
|
|
|
1
|
+
const readline = require('readline');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Lightweight keyboard handler for Auto Mode
|
|
6
|
+
* Listens for 'x' key and Ctrl+C without interfering with child process stdin
|
|
7
|
+
*/
|
|
8
|
+
class KeyboardHandler {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.onExit = options.onExit;
|
|
11
|
+
this.isActive = false;
|
|
12
|
+
this.exitConfirmMode = false;
|
|
13
|
+
this.keypressHandler = null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Start listening for keyboard input
|
|
18
|
+
*/
|
|
19
|
+
start() {
|
|
20
|
+
if (this.isActive) return;
|
|
21
|
+
|
|
22
|
+
this.isActive = true;
|
|
23
|
+
|
|
24
|
+
// Set up keypress events on stdin
|
|
25
|
+
readline.emitKeypressEvents(process.stdin);
|
|
26
|
+
|
|
27
|
+
// Only set raw mode if stdin is a TTY
|
|
28
|
+
if (process.stdin.isTTY && !process.stdin.isRaw) {
|
|
29
|
+
process.stdin.setRawMode(true);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Create keypress handler
|
|
33
|
+
this.keypressHandler = (str, key) => {
|
|
34
|
+
if (!key) return;
|
|
35
|
+
|
|
36
|
+
// Handle Ctrl+C - immediate exit
|
|
37
|
+
if (key.ctrl && key.name === 'c') {
|
|
38
|
+
this.handleImmediateExit();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Handle 'x' key - show confirmation
|
|
43
|
+
if (key.name === 'x' && !this.exitConfirmMode) {
|
|
44
|
+
this.showExitConfirmation();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// In confirmation mode, handle y/n
|
|
49
|
+
if (this.exitConfirmMode) {
|
|
50
|
+
if (key.name === 'y') {
|
|
51
|
+
this.confirmExit();
|
|
52
|
+
} else if (key.name === 'n' || key.name === 'escape') {
|
|
53
|
+
this.cancelExit();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Attach handler
|
|
59
|
+
process.stdin.on('keypress', this.keypressHandler);
|
|
60
|
+
process.stdin.resume();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Stop listening for keyboard input
|
|
65
|
+
*/
|
|
66
|
+
stop() {
|
|
67
|
+
if (!this.isActive) return;
|
|
68
|
+
|
|
69
|
+
this.isActive = false;
|
|
70
|
+
|
|
71
|
+
// Remove keypress handler
|
|
72
|
+
if (this.keypressHandler) {
|
|
73
|
+
process.stdin.removeListener('keypress', this.keypressHandler);
|
|
74
|
+
this.keypressHandler = null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Restore stdin to normal mode
|
|
78
|
+
if (process.stdin.isTTY && process.stdin.isRaw) {
|
|
79
|
+
process.stdin.setRawMode(false);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
process.stdin.pause();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
showExitConfirmation() {
|
|
86
|
+
this.exitConfirmMode = true;
|
|
87
|
+
console.log('\n' + chalk.yellow('─'.repeat(60)));
|
|
88
|
+
console.log(chalk.yellow.bold(' Exit Auto Mode?'));
|
|
89
|
+
console.log(chalk.yellow(' Press ') + chalk.white.bold('y') + chalk.yellow(' to exit, ') + chalk.white.bold('n') + chalk.yellow(' to cancel'));
|
|
90
|
+
console.log(chalk.yellow('─'.repeat(60)) + '\n');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
cancelExit() {
|
|
94
|
+
this.exitConfirmMode = false;
|
|
95
|
+
console.log(chalk.gray(' Cancelled - continuing Auto Mode...\n'));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
confirmExit() {
|
|
99
|
+
this.exitConfirmMode = false;
|
|
100
|
+
this.stop();
|
|
101
|
+
|
|
102
|
+
console.log(chalk.green(' Exiting Auto Mode...\n'));
|
|
103
|
+
|
|
104
|
+
if (this.onExit) {
|
|
105
|
+
this.onExit();
|
|
106
|
+
} else {
|
|
107
|
+
process.exit(0);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
handleImmediateExit() {
|
|
112
|
+
console.log(chalk.yellow('\n Ctrl+C detected - force exiting...\n'));
|
|
113
|
+
|
|
114
|
+
// Call onExit callback first if provided (for cleanup)
|
|
115
|
+
if (this.onExit) {
|
|
116
|
+
try {
|
|
117
|
+
this.onExit();
|
|
118
|
+
} catch (err) {
|
|
119
|
+
// Ignore errors during cleanup
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Force exit immediately - don't wait for async cleanup
|
|
124
|
+
// This is critical because the event loop may be blocked on child processes
|
|
125
|
+
this.stop();
|
|
126
|
+
process.exit(0);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Create a keyboard handler for Auto Mode
|
|
132
|
+
* @param {object} options - Configuration options
|
|
133
|
+
* @param {function} options.onExit - Callback when user exits
|
|
134
|
+
* @returns {KeyboardHandler}
|
|
135
|
+
*/
|
|
136
|
+
function createKeyboardHandler(options = {}) {
|
|
137
|
+
return new KeyboardHandler(options);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = {
|
|
141
|
+
KeyboardHandler,
|
|
142
|
+
createKeyboardHandler
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Simple persistent header that stays at top while content scrolls below
|
|
5
|
+
* Uses ANSI terminal control codes instead of blessed
|
|
6
|
+
*/
|
|
7
|
+
class PersistentHeader {
|
|
8
|
+
constructor(lines = 10) {
|
|
9
|
+
this.headerLines = lines;
|
|
10
|
+
this.headerContent = '';
|
|
11
|
+
this.isActive = false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Initialize the header by saving cursor and creating space
|
|
16
|
+
*/
|
|
17
|
+
start() {
|
|
18
|
+
if (this.isActive) return;
|
|
19
|
+
this.isActive = true;
|
|
20
|
+
|
|
21
|
+
// Clear screen and move to top
|
|
22
|
+
process.stdout.write('\x1B[2J\x1B[H');
|
|
23
|
+
|
|
24
|
+
// Create space for header by printing empty lines
|
|
25
|
+
for (let i = 0; i < this.headerLines; i++) {
|
|
26
|
+
process.stdout.write('\n');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Update the header content
|
|
32
|
+
* @param {string} content - New header content
|
|
33
|
+
*/
|
|
34
|
+
update(content) {
|
|
35
|
+
if (!this.isActive) return;
|
|
36
|
+
|
|
37
|
+
this.headerContent = content;
|
|
38
|
+
|
|
39
|
+
// Save current cursor position
|
|
40
|
+
process.stdout.write('\x1B[s');
|
|
41
|
+
|
|
42
|
+
// Move to top of screen
|
|
43
|
+
process.stdout.write('\x1B[H');
|
|
44
|
+
|
|
45
|
+
// Clear the header area
|
|
46
|
+
for (let i = 0; i < this.headerLines; i++) {
|
|
47
|
+
process.stdout.write('\x1B[2K'); // Clear line
|
|
48
|
+
if (i < this.headerLines - 1) process.stdout.write('\n');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Move back to top
|
|
52
|
+
process.stdout.write('\x1B[H');
|
|
53
|
+
|
|
54
|
+
// Write header content
|
|
55
|
+
process.stdout.write(content);
|
|
56
|
+
|
|
57
|
+
// Restore cursor position
|
|
58
|
+
process.stdout.write('\x1B[u');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Stop the persistent header
|
|
63
|
+
*/
|
|
64
|
+
stop() {
|
|
65
|
+
if (!this.isActive) return;
|
|
66
|
+
this.isActive = false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Format auto mode header
|
|
72
|
+
*/
|
|
73
|
+
function formatAutoModeHeader(status) {
|
|
74
|
+
const {
|
|
75
|
+
requirement = 'No requirement',
|
|
76
|
+
step = 'PREPARE',
|
|
77
|
+
chatCount = 0,
|
|
78
|
+
maxChats = null,
|
|
79
|
+
progress = 0,
|
|
80
|
+
repoPath = '',
|
|
81
|
+
hostname = ''
|
|
82
|
+
} = status;
|
|
83
|
+
|
|
84
|
+
const stepColors = {
|
|
85
|
+
'PREPARE': chalk.cyan,
|
|
86
|
+
'ACT': chalk.yellow,
|
|
87
|
+
'CLEAN UP': chalk.magenta,
|
|
88
|
+
'VERIFY': chalk.blue,
|
|
89
|
+
'DONE': chalk.green,
|
|
90
|
+
'UNKNOWN': chalk.gray
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const stepColor = stepColors[step] || chalk.gray;
|
|
94
|
+
|
|
95
|
+
const chatDisplay = maxChats
|
|
96
|
+
? `Chat ${chatCount}/${maxChats}`
|
|
97
|
+
: `Chat ${chatCount} (unlimited)`;
|
|
98
|
+
|
|
99
|
+
const progressBar = chalk.green('█'.repeat(Math.floor(progress / 5))) +
|
|
100
|
+
chalk.gray('░'.repeat(20 - Math.floor(progress / 5)));
|
|
101
|
+
|
|
102
|
+
return `${chalk.cyan('━'.repeat(80))}
|
|
103
|
+
${chalk.bold.cyan(' VibeCodingMachine Auto Mode')} ${chalk.gray('- Press Ctrl+C to stop')}
|
|
104
|
+
${chalk.cyan('━'.repeat(80))}
|
|
105
|
+
${chalk.bold(' Requirement:')} ${requirement.substring(0, 60)}
|
|
106
|
+
${chalk.bold(' Status:')} ${stepColor(step)} ${chalk.bold('Progress:')} ${progressBar} ${progress}%
|
|
107
|
+
${chalk.bold(' Chats:')} ${chatDisplay}
|
|
108
|
+
${chalk.cyan('━'.repeat(80))}
|
|
109
|
+
|
|
110
|
+
`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = {
|
|
114
|
+
PersistentHeader,
|
|
115
|
+
formatAutoModeHeader
|
|
116
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const { getAutoConfig, setAutoConfig } = require('./config');
|
|
4
|
+
|
|
5
|
+
const PROVIDER_DEFINITIONS = [
|
|
6
|
+
{
|
|
7
|
+
id: 'groq',
|
|
8
|
+
name: 'Groq (Cloud - Very Fast)',
|
|
9
|
+
type: 'direct',
|
|
10
|
+
category: 'llm',
|
|
11
|
+
defaultModel: 'llama-3.3-70b-versatile',
|
|
12
|
+
configKeys: { apiKey: 'groqApiKey', model: 'groqModel' },
|
|
13
|
+
estimatedSpeed: 15000
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 'anthropic',
|
|
17
|
+
name: 'Anthropic (Claude Sonnet 4)',
|
|
18
|
+
type: 'direct',
|
|
19
|
+
category: 'llm',
|
|
20
|
+
defaultModel: 'claude-sonnet-4-20250514',
|
|
21
|
+
configKeys: { apiKey: 'anthropicApiKey', model: 'anthropicModel' },
|
|
22
|
+
estimatedSpeed: 25000
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'bedrock',
|
|
26
|
+
name: 'AWS Bedrock (Claude)',
|
|
27
|
+
type: 'direct',
|
|
28
|
+
category: 'llm',
|
|
29
|
+
defaultModel: 'anthropic.claude-sonnet-4-v1',
|
|
30
|
+
configKeys: { region: 'awsRegion', accessKeyId: 'awsAccessKeyId', secretAccessKey: 'awsSecretAccessKey' },
|
|
31
|
+
estimatedSpeed: 40000
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'claude-code',
|
|
35
|
+
name: 'Claude Code CLI (Sonnet 4.5)',
|
|
36
|
+
type: 'direct',
|
|
37
|
+
category: 'llm',
|
|
38
|
+
defaultModel: 'claude-code-cli',
|
|
39
|
+
estimatedSpeed: 35000
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'cursor',
|
|
43
|
+
name: 'Cursor IDE Agent',
|
|
44
|
+
type: 'ide',
|
|
45
|
+
category: 'ide',
|
|
46
|
+
ide: 'cursor',
|
|
47
|
+
estimatedSpeed: 60000
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'windsurf',
|
|
51
|
+
name: 'Windsurf IDE Agent',
|
|
52
|
+
type: 'ide',
|
|
53
|
+
category: 'ide',
|
|
54
|
+
ide: 'windsurf',
|
|
55
|
+
estimatedSpeed: 90000
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'antigravity',
|
|
59
|
+
name: 'Google Antigravity IDE Agent',
|
|
60
|
+
type: 'ide',
|
|
61
|
+
category: 'ide',
|
|
62
|
+
ide: 'antigravity',
|
|
63
|
+
estimatedSpeed: 90000
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'ollama',
|
|
67
|
+
name: 'Ollama (Local)',
|
|
68
|
+
type: 'direct',
|
|
69
|
+
category: 'llm',
|
|
70
|
+
defaultModel: 'qwen2.5-coder:32b',
|
|
71
|
+
estimatedSpeed: 200000
|
|
72
|
+
}
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
function getProviderDefinitions() {
|
|
76
|
+
return PROVIDER_DEFINITIONS.map(def => ({ ...def }));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getProviderDefinition(id) {
|
|
80
|
+
return PROVIDER_DEFINITIONS.find(def => def.id === id);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getDefaultProviderOrder() {
|
|
84
|
+
return PROVIDER_DEFINITIONS.map(def => def.id);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function mergeProviderPreferences(autoConfig) {
|
|
88
|
+
const prefs = (autoConfig && autoConfig.providerPreferences) || {};
|
|
89
|
+
const order = Array.isArray(prefs.order) ? prefs.order.slice() : [];
|
|
90
|
+
const enabled = (prefs.enabled && typeof prefs.enabled === 'object') ? { ...prefs.enabled } : {};
|
|
91
|
+
|
|
92
|
+
const defaultOrder = getDefaultProviderOrder();
|
|
93
|
+
const cleanedOrder = order.filter(id => getProviderDefinition(id));
|
|
94
|
+
defaultOrder.forEach(id => {
|
|
95
|
+
if (!cleanedOrder.includes(id)) {
|
|
96
|
+
cleanedOrder.push(id);
|
|
97
|
+
}
|
|
98
|
+
if (enabled[id] === undefined) {
|
|
99
|
+
enabled[id] = true;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return { order: cleanedOrder, enabled };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function getProviderPreferences() {
|
|
107
|
+
const autoConfig = await getAutoConfig();
|
|
108
|
+
return mergeProviderPreferences(autoConfig);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function saveProviderPreferences(order, enabled) {
|
|
112
|
+
await setAutoConfig({
|
|
113
|
+
providerPreferences: {
|
|
114
|
+
order: order.slice(),
|
|
115
|
+
enabled: { ...enabled }
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = {
|
|
121
|
+
getProviderDefinitions,
|
|
122
|
+
getProviderDefinition,
|
|
123
|
+
getDefaultProviderOrder,
|
|
124
|
+
mergeProviderPreferences,
|
|
125
|
+
getProviderPreferences,
|
|
126
|
+
saveProviderPreferences
|
|
127
|
+
};
|
|
128
|
+
|
|
File without changes
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const boxen = require('boxen');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Render a status card showing current requirement progress
|
|
6
|
+
* Similar to the purple card in the GUI
|
|
7
|
+
* @param {object} status - Current status object
|
|
8
|
+
* @param {string} status.requirement - Current requirement being worked on
|
|
9
|
+
* @param {string} status.step - Current step (PREPARE, ACT, CLEAN UP, VERIFY, DONE)
|
|
10
|
+
* @param {number} status.chatCount - Current chat count
|
|
11
|
+
* @param {number|null} status.maxChats - Maximum chats or null for unlimited
|
|
12
|
+
* @param {number} status.progress - Progress percentage (0-100)
|
|
13
|
+
* @returns {string} Formatted status card
|
|
14
|
+
*/
|
|
15
|
+
function renderStatusCard(status) {
|
|
16
|
+
const {
|
|
17
|
+
requirement = 'No requirement loaded',
|
|
18
|
+
step = 'UNKNOWN',
|
|
19
|
+
chatCount = 0,
|
|
20
|
+
maxChats = null,
|
|
21
|
+
progress = 0
|
|
22
|
+
} = status;
|
|
23
|
+
|
|
24
|
+
// Step color mapping
|
|
25
|
+
const stepColors = {
|
|
26
|
+
'PREPARE': chalk.cyan,
|
|
27
|
+
'ACT': chalk.yellow,
|
|
28
|
+
'CLEAN UP': chalk.magenta,
|
|
29
|
+
'VERIFY': chalk.blue,
|
|
30
|
+
'DONE': chalk.green,
|
|
31
|
+
'UNKNOWN': chalk.gray
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const stepColor = stepColors[step] || chalk.gray;
|
|
35
|
+
|
|
36
|
+
// Progress bar
|
|
37
|
+
const barWidth = 30;
|
|
38
|
+
const filledWidth = Math.round((progress / 100) * barWidth);
|
|
39
|
+
const emptyWidth = barWidth - filledWidth;
|
|
40
|
+
const progressBar = chalk.green('█'.repeat(filledWidth)) + chalk.gray('░'.repeat(emptyWidth));
|
|
41
|
+
|
|
42
|
+
// Chat counter
|
|
43
|
+
const chatDisplay = maxChats
|
|
44
|
+
? `Chat ${chatCount}/${maxChats}`
|
|
45
|
+
: `Chat ${chatCount} (unlimited)`;
|
|
46
|
+
|
|
47
|
+
// Build card content
|
|
48
|
+
const content = [
|
|
49
|
+
chalk.bold('📋 Current Requirement'),
|
|
50
|
+
'',
|
|
51
|
+
chalk.white(requirement.length > 60 ? requirement.substring(0, 57) + '...' : requirement),
|
|
52
|
+
'',
|
|
53
|
+
chalk.bold('🚦 Status: ') + stepColor.bold(step),
|
|
54
|
+
'',
|
|
55
|
+
`${progressBar} ${progress}%`,
|
|
56
|
+
'',
|
|
57
|
+
chalk.gray(chatDisplay)
|
|
58
|
+
].join('\n');
|
|
59
|
+
|
|
60
|
+
// Render with boxen (purple/magenta border like the GUI)
|
|
61
|
+
return boxen(content, {
|
|
62
|
+
padding: 1,
|
|
63
|
+
margin: { top: 0, right: 0, bottom: 1, left: 0 },
|
|
64
|
+
borderStyle: 'round',
|
|
65
|
+
borderColor: 'magenta',
|
|
66
|
+
title: 'Auto Mode Status',
|
|
67
|
+
titleAlignment: 'center'
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Clear the terminal and move cursor to top
|
|
73
|
+
*/
|
|
74
|
+
function clearAndMoveToTop() {
|
|
75
|
+
// ANSI escape codes
|
|
76
|
+
process.stdout.write('\x1B[2J'); // Clear entire screen
|
|
77
|
+
process.stdout.write('\x1B[H'); // Move cursor to home (top-left)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Move cursor up N lines
|
|
82
|
+
* @param {number} lines - Number of lines to move up
|
|
83
|
+
*/
|
|
84
|
+
function moveCursorUp(lines) {
|
|
85
|
+
process.stdout.write(`\x1B[${lines}A`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Save cursor position
|
|
90
|
+
*/
|
|
91
|
+
function saveCursor() {
|
|
92
|
+
process.stdout.write('\x1B[s');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Restore cursor position
|
|
97
|
+
*/
|
|
98
|
+
function restoreCursor() {
|
|
99
|
+
process.stdout.write('\x1B[u');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Render the menu header and status card together
|
|
104
|
+
* @param {string} menuContent - The menu content to display
|
|
105
|
+
* @param {object} status - Status object for the status card
|
|
106
|
+
*/
|
|
107
|
+
function renderHeaderWithStatus(menuContent, status) {
|
|
108
|
+
clearAndMoveToTop();
|
|
109
|
+
console.log(menuContent);
|
|
110
|
+
console.log(renderStatusCard(status));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = {
|
|
114
|
+
renderStatusCard,
|
|
115
|
+
clearAndMoveToTop,
|
|
116
|
+
moveCursorUp,
|
|
117
|
+
saveCursor,
|
|
118
|
+
restoreCursor,
|
|
119
|
+
renderHeaderWithStatus
|
|
120
|
+
};
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intercept stdout and stderr to capture console output
|
|
3
|
+
* Allows routing output to custom handlers (like blessed UI) while optionally preserving original output
|
|
4
|
+
*/
|
|
5
|
+
class StdoutInterceptor {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.originalStdoutWrite = null;
|
|
8
|
+
this.originalStderrWrite = null;
|
|
9
|
+
this.outputHandlers = [];
|
|
10
|
+
this.isIntercepting = false;
|
|
11
|
+
this.buffer = '<span style="color: white;">';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Add a handler that will receive output
|
|
16
|
+
* @param {function} handler - Function that receives output strings
|
|
17
|
+
*/
|
|
18
|
+
addHandler(handler) {
|
|
19
|
+
this.outputHandlers.push(handler);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Remove a handler
|
|
24
|
+
* @param {function} handler - Handler to remove
|
|
25
|
+
*/
|
|
26
|
+
removeHandler(handler) {
|
|
27
|
+
const index = this.outputHandlers.indexOf(handler);
|
|
28
|
+
if (index > -1) {
|
|
29
|
+
this.outputHandlers.splice(index, 1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Start intercepting stdout/stderr
|
|
35
|
+
* @param {boolean} passthrough - If true, still write to original stdout/stderr
|
|
36
|
+
*/
|
|
37
|
+
start(passthrough = false) {
|
|
38
|
+
if (this.isIntercepting) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.isIntercepting = true;
|
|
43
|
+
|
|
44
|
+
// Save original write functions
|
|
45
|
+
this.originalStdoutWrite = process.stdout.write;
|
|
46
|
+
this.originalStderrWrite = process.stderr.write;
|
|
47
|
+
|
|
48
|
+
// Intercept stdout
|
|
49
|
+
process.stdout.write = (chunk, encoding, callback) => {
|
|
50
|
+
const str = chunk.toString();
|
|
51
|
+
|
|
52
|
+
// Call all registered handlers
|
|
53
|
+
this.outputHandlers.forEach(handler => {
|
|
54
|
+
try {
|
|
55
|
+
handler(str);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
// Silently ignore handler errors to prevent breaking the interceptor
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Optionally pass through to original stdout
|
|
62
|
+
if (passthrough && this.originalStdoutWrite) {
|
|
63
|
+
return this.originalStdoutWrite.call(process.stdout, chunk, encoding, callback);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// If not passing through, still call the callback if provided
|
|
67
|
+
if (typeof callback === 'function') {
|
|
68
|
+
callback();
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Intercept stderr
|
|
74
|
+
process.stderr.write = (chunk, encoding, callback) => {
|
|
75
|
+
const str = chunk.toString();
|
|
76
|
+
|
|
77
|
+
// Call all registered handlers
|
|
78
|
+
this.outputHandlers.forEach(handler => {
|
|
79
|
+
try {
|
|
80
|
+
handler(str);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
// Silently ignore handler errors
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Optionally pass through to original stderr
|
|
87
|
+
if (passthrough && this.originalStderrWrite) {
|
|
88
|
+
return this.originalStderrWrite.call(process.stderr, chunk, encoding, callback);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// If not passing through, still call the callback if provided
|
|
92
|
+
if (typeof callback === 'function') {
|
|
93
|
+
callback();
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Stop intercepting and restore original stdout/stderr
|
|
101
|
+
*/
|
|
102
|
+
stop() {
|
|
103
|
+
if (!this.isIntercepting) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (this.originalStdoutWrite) {
|
|
108
|
+
process.stdout.write = this.originalStdoutWrite;
|
|
109
|
+
}
|
|
110
|
+
if (this.originalStderrWrite) {
|
|
111
|
+
process.stderr.write = this.originalStderrWrite;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.isIntercepting = false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Clear all handlers
|
|
119
|
+
*/
|
|
120
|
+
clearHandlers() {
|
|
121
|
+
this.outputHandlers = [];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = {
|
|
126
|
+
StdoutInterceptor
|
|
127
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
|
|
5
|
+
describe('auto-mode utils', () => {
|
|
6
|
+
const tmpConfig = path.join(os.tmpdir(), `allnightai_test_config_${Date.now()}.json`);
|
|
7
|
+
const tmpRepo = path.join(os.tmpdir(), `allnightai_test_repo_${Date.now()}`);
|
|
8
|
+
|
|
9
|
+
beforeAll(async () => {
|
|
10
|
+
process.env.ALLNIGHTAI_CONFIG_PATH = tmpConfig;
|
|
11
|
+
await fs.ensureDir(path.join(tmpRepo, '.vibecodingmachine', 'temp'));
|
|
12
|
+
const { setRepoPath } = require('../src/utils/config');
|
|
13
|
+
await setRepoPath(tmpRepo);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterAll(async () => {
|
|
17
|
+
delete process.env.ALLNIGHTAI_CONFIG_PATH;
|
|
18
|
+
await fs.remove(tmpConfig).catch(() => {});
|
|
19
|
+
await fs.remove(tmpRepo).catch(() => {});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('start/stop and status', async () => {
|
|
23
|
+
const { startAutoMode, stopAutoMode, checkAutoModeStatus } = require('../src/utils/auto-mode');
|
|
24
|
+
const { getAutoConfig } = require('../src/utils/config');
|
|
25
|
+
await startAutoMode(tmpRepo, { ide: 'cursor' });
|
|
26
|
+
let status = await checkAutoModeStatus();
|
|
27
|
+
expect(status.running).toBe(true);
|
|
28
|
+
expect(status.ide).toBe('cursor');
|
|
29
|
+
|
|
30
|
+
await stopAutoMode();
|
|
31
|
+
status = await checkAutoModeStatus();
|
|
32
|
+
expect(status.running).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|