vibecodingmachine-core 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/.babelrc +13 -0
- package/README.md +28 -0
- package/__tests__/applescript-manager-claude-fix.test.js +286 -0
- package/__tests__/requirement-2-auto-start-looping.test.js +69 -0
- package/__tests__/requirement-3-auto-start-looping.test.js +69 -0
- package/__tests__/requirement-4-auto-start-looping.test.js +69 -0
- package/__tests__/requirement-6-auto-start-looping.test.js +73 -0
- package/__tests__/requirement-7-status-tracking.test.js +332 -0
- package/jest.config.js +18 -0
- package/jest.setup.js +12 -0
- package/package.json +46 -0
- package/src/auth/access-denied.html +119 -0
- package/src/auth/shared-auth-storage.js +230 -0
- package/src/autonomous-mode/feature-implementer.cjs +70 -0
- package/src/autonomous-mode/feature-implementer.js +425 -0
- package/src/chat-management/chat-manager.cjs +71 -0
- package/src/chat-management/chat-manager.js +342 -0
- package/src/ide-integration/__tests__/applescript-manager-thread-closure.test.js +227 -0
- package/src/ide-integration/aider-cli-manager.cjs +850 -0
- package/src/ide-integration/applescript-diagnostics.js +0 -0
- package/src/ide-integration/applescript-manager.cjs +1088 -0
- package/src/ide-integration/applescript-manager.js +2803 -0
- package/src/ide-integration/applescript-open-apps.js +0 -0
- package/src/ide-integration/applescript-read-response.js +0 -0
- package/src/ide-integration/applescript-send-text.js +0 -0
- package/src/ide-integration/applescript-thread-closure.js +0 -0
- package/src/ide-integration/applescript-utils.js +306 -0
- package/src/ide-integration/cdp-manager.cjs +221 -0
- package/src/ide-integration/cdp-manager.js +321 -0
- package/src/ide-integration/claude-code-cli-manager.cjs +301 -0
- package/src/ide-integration/cline-cli-manager.cjs +2252 -0
- package/src/ide-integration/continue-cli-manager.js +431 -0
- package/src/ide-integration/provider-manager.cjs +354 -0
- package/src/ide-integration/quota-detector.cjs +34 -0
- package/src/ide-integration/quota-detector.js +349 -0
- package/src/ide-integration/windows-automation-manager.js +262 -0
- package/src/index.cjs +43 -0
- package/src/index.js +17 -0
- package/src/llm/direct-llm-manager.cjs +609 -0
- package/src/ui/ButtonComponents.js +247 -0
- package/src/ui/ChatInterface.js +499 -0
- package/src/ui/StateManager.js +259 -0
- package/src/ui/StateManager.test.js +0 -0
- package/src/utils/audit-logger.cjs +116 -0
- package/src/utils/config-helpers.cjs +94 -0
- package/src/utils/config-helpers.js +94 -0
- package/src/utils/electron-update-checker.js +78 -0
- package/src/utils/gcloud-auth.cjs +394 -0
- package/src/utils/logger.cjs +193 -0
- package/src/utils/logger.js +191 -0
- package/src/utils/repo-helpers.cjs +120 -0
- package/src/utils/repo-helpers.js +120 -0
- package/src/utils/requirement-helpers.js +432 -0
- package/src/utils/update-checker.js +167 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import CDP from 'chrome-remote-interface';
|
|
2
|
+
|
|
3
|
+
// Timeout utility function
|
|
4
|
+
function timeout(ms, promise) {
|
|
5
|
+
return Promise.race([
|
|
6
|
+
promise,
|
|
7
|
+
new Promise((_, reject) =>
|
|
8
|
+
setTimeout(() => reject(new Error(`Operation timed out after ${ms}ms`)), ms)
|
|
9
|
+
)
|
|
10
|
+
]);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class CDPManager {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.ports = {
|
|
16
|
+
vscode: 9222,
|
|
17
|
+
cursor: 9225,
|
|
18
|
+
windsurf: 9224
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Connect to an IDE via CDP
|
|
24
|
+
* @param {string} ide - The IDE name (vscode, cursor, windsurf)
|
|
25
|
+
* @param {number} port - The CDP port (optional, will use default if not provided)
|
|
26
|
+
* @returns {Promise<Object>} CDP client and domains
|
|
27
|
+
*/
|
|
28
|
+
async connectToIDE(ide, port = null) {
|
|
29
|
+
const targetPort = port || this.ports[ide];
|
|
30
|
+
|
|
31
|
+
if (!targetPort) {
|
|
32
|
+
throw new Error(`No CDP port configured for IDE: ${ide}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
console.log(`🔍 Connecting to ${ide} on port ${targetPort}...`);
|
|
37
|
+
|
|
38
|
+
const targets = await timeout(5000, CDP.List({ port: targetPort }));
|
|
39
|
+
|
|
40
|
+
if (!targets || targets.length === 0) {
|
|
41
|
+
throw new Error(`Could not find ${ide}. Make sure ${ide} is running with --remote-debugging-port=${targetPort}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Find the appropriate target
|
|
45
|
+
let target;
|
|
46
|
+
if (ide === 'vscode') {
|
|
47
|
+
target = targets.find(t => t.url && t.url.includes('workbench')) || targets[0];
|
|
48
|
+
} else {
|
|
49
|
+
target = targets[0];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!target) {
|
|
53
|
+
throw new Error(`No suitable target found for ${ide}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log(`Found ${targets.length} targets, using: ${target.title}`);
|
|
57
|
+
|
|
58
|
+
const client = await CDP({ port: targetPort, target });
|
|
59
|
+
const { Runtime, Input, Page, DOM } = client;
|
|
60
|
+
|
|
61
|
+
await Runtime.enable();
|
|
62
|
+
await DOM.enable();
|
|
63
|
+
|
|
64
|
+
if (Page && Page.bringToFront) {
|
|
65
|
+
try {
|
|
66
|
+
await Page.bringToFront();
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.log('Could not bring page to front:', error.message);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
client,
|
|
74
|
+
Runtime,
|
|
75
|
+
Input,
|
|
76
|
+
Page,
|
|
77
|
+
DOM,
|
|
78
|
+
target
|
|
79
|
+
};
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error(`CDP connection error for ${ide}:`, error);
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Send text to an IDE via CDP
|
|
88
|
+
* @param {string} text - The text to send
|
|
89
|
+
* @param {string} ide - The IDE name
|
|
90
|
+
* @returns {Promise<Object>} Result of the operation
|
|
91
|
+
*/
|
|
92
|
+
async sendText(text, ide) {
|
|
93
|
+
if (typeof text !== 'string') {
|
|
94
|
+
return {
|
|
95
|
+
success: false,
|
|
96
|
+
error: `Invalid text type: ${typeof text}. Expected string.`
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const ideName = ide === 'windsurf' ? 'Windsurf' : ide === 'cursor' ? 'Cursor' : 'VS Code';
|
|
101
|
+
|
|
102
|
+
console.log(`🔍 sendText: Starting debug session`);
|
|
103
|
+
console.log(`📋 IDE: ${ideName} (${ide})`);
|
|
104
|
+
console.log(`💬 Text: "${text.substring(0, 100)}${text.length > 100 ? '...' : ''}"`);
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const { client, Runtime, Input } = await this.connectToIDE(ide);
|
|
108
|
+
|
|
109
|
+
// VS Code specific handling
|
|
110
|
+
if (ide === 'vscode') {
|
|
111
|
+
return await this.sendTextToVSCode(text, Runtime, Input);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// For other IDEs, use basic CDP approach
|
|
115
|
+
return await this.sendTextBasic(text, Runtime, Input);
|
|
116
|
+
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error(`${ideName} CDP error:`, error);
|
|
119
|
+
return {
|
|
120
|
+
success: false,
|
|
121
|
+
error: `${ideName} CDP error: ${error.message}`
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Send text to VS Code using the working approach
|
|
128
|
+
* @param {string} text - The text to send
|
|
129
|
+
* @param {Object} Runtime - CDP Runtime domain
|
|
130
|
+
* @param {Object} Input - CDP Input domain
|
|
131
|
+
* @returns {Promise<Object>} Result of the operation
|
|
132
|
+
*/
|
|
133
|
+
async sendTextToVSCode(text, Runtime, Input) {
|
|
134
|
+
try {
|
|
135
|
+
try {
|
|
136
|
+
const isDarwin = process.platform === 'darwin';
|
|
137
|
+
const modifiers = (isDarwin ? 4 /* Meta */ | 2 /* Ctrl */ : 2 /* Ctrl */);
|
|
138
|
+
await Input.dispatchKeyEvent({ type: 'keyDown', key: 'I', text: 'I', modifiers });
|
|
139
|
+
await Input.dispatchKeyEvent({ type: 'keyUp', key: 'I', text: 'I', modifiers });
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.log('Could not activate chat view:', error.message);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const textToSend = String(text).slice(0, 4000);
|
|
145
|
+
|
|
146
|
+
// Use clipboard approach to avoid focus issues
|
|
147
|
+
console.log('Copying text to clipboard:', textToSend);
|
|
148
|
+
|
|
149
|
+
// Note: In VSCode extension context, we'll need to handle clipboard differently
|
|
150
|
+
// For now, we'll use character-by-character typing
|
|
151
|
+
|
|
152
|
+
// Type the text character by character
|
|
153
|
+
console.log('Starting character-by-character typing...');
|
|
154
|
+
for (let i = 0; i < textToSend.length; i++) {
|
|
155
|
+
const char = textToSend[i];
|
|
156
|
+
await Input.dispatchKeyEvent({ type: 'keyDown', key: char, text: char });
|
|
157
|
+
await Input.dispatchKeyEvent({ type: 'keyUp', key: char, text: char });
|
|
158
|
+
await new Promise(resolve => setTimeout(resolve, 20)); // Small delay between characters
|
|
159
|
+
}
|
|
160
|
+
console.log('Finished character-by-character typing');
|
|
161
|
+
|
|
162
|
+
// Submit the message
|
|
163
|
+
await this.submitMessage(Input, Runtime);
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
success: true,
|
|
167
|
+
method: 'cdp-character-typing',
|
|
168
|
+
message: `Message sent to VS Code: ${text}`,
|
|
169
|
+
note: 'Message sent via CDP with character-by-character typing'
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error('VS Code CDP error:', error);
|
|
174
|
+
return { success: false, error: `VS Code CDP error: ${error.message}` };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Send text using basic CDP approach
|
|
180
|
+
* @param {string} text - The text to send
|
|
181
|
+
* @param {Object} Runtime - CDP Runtime domain
|
|
182
|
+
* @param {Object} Input - CDP Input domain
|
|
183
|
+
* @returns {Promise<Object>} Result of the operation
|
|
184
|
+
*/
|
|
185
|
+
async sendTextBasic(text, Runtime, Input) {
|
|
186
|
+
try {
|
|
187
|
+
const textToSend = String(text).slice(0, 4000);
|
|
188
|
+
|
|
189
|
+
// Type the text character by character
|
|
190
|
+
console.log('Starting character-by-character typing...');
|
|
191
|
+
for (let i = 0; i < textToSend.length; i++) {
|
|
192
|
+
const char = textToSend[i];
|
|
193
|
+
await Input.dispatchKeyEvent({ type: 'keyDown', key: char, text: char });
|
|
194
|
+
await Input.dispatchKeyEvent({ type: 'keyUp', key: char, text: char });
|
|
195
|
+
await new Promise(resolve => setTimeout(resolve, 20));
|
|
196
|
+
}
|
|
197
|
+
console.log('Finished character-by-character typing');
|
|
198
|
+
|
|
199
|
+
// Submit the message
|
|
200
|
+
await this.submitMessage(Input, Runtime);
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
success: true,
|
|
204
|
+
method: 'cdp-basic',
|
|
205
|
+
message: `Message sent: ${text}`,
|
|
206
|
+
note: 'Message sent via basic CDP approach'
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
} catch (error) {
|
|
210
|
+
console.error('Basic CDP error:', error);
|
|
211
|
+
return { success: false, error: `Basic CDP error: ${error.message}` };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Submit a message using multiple methods
|
|
217
|
+
* @param {Object} Input - CDP Input domain
|
|
218
|
+
* @param {Object} Runtime - CDP Runtime domain
|
|
219
|
+
* @returns {Promise<void>}
|
|
220
|
+
*/
|
|
221
|
+
async submitMessage(Input, Runtime) {
|
|
222
|
+
// Wait a moment then try multiple ways to submit the message
|
|
223
|
+
setTimeout(async () => {
|
|
224
|
+
try {
|
|
225
|
+
console.log('Attempting to submit message...');
|
|
226
|
+
|
|
227
|
+
// Method 1: Press Enter
|
|
228
|
+
console.log('Method 1: Pressing Enter...');
|
|
229
|
+
await Input.dispatchKeyEvent({ type: 'keyDown', key: 'Enter' });
|
|
230
|
+
await Input.dispatchKeyEvent({ type: 'keyUp', key: 'Enter' });
|
|
231
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
232
|
+
|
|
233
|
+
// Method 2: Try clicking the send button
|
|
234
|
+
console.log('Method 2: Looking for send button...');
|
|
235
|
+
const clickSendButton = `(() => {
|
|
236
|
+
const sendButton = document.querySelector('[aria-label*="Send"]') ||
|
|
237
|
+
document.querySelector('[title*="Send"]') ||
|
|
238
|
+
document.querySelector('button[aria-label*="submit"]') ||
|
|
239
|
+
document.querySelector('button[title*="submit"]') ||
|
|
240
|
+
document.querySelector('[aria-label*="Send message"]') ||
|
|
241
|
+
document.querySelector('[title*="Send message"]') ||
|
|
242
|
+
document.querySelector('button[aria-label*="Send message"]') ||
|
|
243
|
+
document.querySelector('button[title*="Send message"]') ||
|
|
244
|
+
document.querySelector('button[aria-label*="Submit"]') ||
|
|
245
|
+
document.querySelector('button[title*="Submit"]') ||
|
|
246
|
+
document.querySelector('button[aria-label*="Send request"]') ||
|
|
247
|
+
document.querySelector('button[title*="Send request"]') ||
|
|
248
|
+
document.querySelector('button svg[data-icon*="send"]')?.closest('button') ||
|
|
249
|
+
document.querySelector('button svg[data-icon*="play"]')?.closest('button') ||
|
|
250
|
+
document.querySelector('button svg[data-icon*="arrow"]')?.closest('button') ||
|
|
251
|
+
document.querySelector('button.send-button') ||
|
|
252
|
+
document.querySelector('button.submit-button') ||
|
|
253
|
+
document.querySelector('button[class*="send"]') ||
|
|
254
|
+
document.querySelector('button[class*="submit"]');
|
|
255
|
+
|
|
256
|
+
if (sendButton) {
|
|
257
|
+
console.log('Found send button:', sendButton.getAttribute('aria-label') || sendButton.getAttribute('title') || sendButton.className);
|
|
258
|
+
sendButton.click();
|
|
259
|
+
return true;
|
|
260
|
+
} else {
|
|
261
|
+
console.log('No send button found');
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
})()`;
|
|
265
|
+
|
|
266
|
+
const { result: clickRes } = await Runtime.evaluate({ expression: clickSendButton, returnByValue: true });
|
|
267
|
+
const clickResult = clickRes && (clickRes.value ?? clickRes.unserializableValue) ? (clickRes.value ?? clickRes.unserializableValue) : false;
|
|
268
|
+
console.log('Send button click result:', clickResult);
|
|
269
|
+
|
|
270
|
+
// Method 3: Try Cmd+Enter as alternative
|
|
271
|
+
if (!clickResult) {
|
|
272
|
+
console.log('Method 3: Trying Cmd+Enter...');
|
|
273
|
+
const isDarwin = process.platform === 'darwin';
|
|
274
|
+
const enterMods = isDarwin ? 4 /* Meta */ : 2 /* Ctrl */;
|
|
275
|
+
await Input.dispatchKeyEvent({ type: 'keyDown', key: 'Enter', text: 'Enter', modifiers: enterMods });
|
|
276
|
+
await Input.dispatchKeyEvent({ type: 'keyUp', key: 'Enter', text: 'Enter', modifiers: enterMods });
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
console.log('All submission methods attempted');
|
|
280
|
+
} catch (error) {
|
|
281
|
+
console.log('Submission failed:', error.message);
|
|
282
|
+
}
|
|
283
|
+
}, 300);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Read chat text from an IDE
|
|
288
|
+
* @param {string} ide - The IDE name
|
|
289
|
+
* @returns {Promise<string>} The chat text
|
|
290
|
+
*/
|
|
291
|
+
async readChatText(ide) {
|
|
292
|
+
try {
|
|
293
|
+
const { Runtime } = await this.connectToIDE(ide);
|
|
294
|
+
|
|
295
|
+
const expression = `(() => {
|
|
296
|
+
try {
|
|
297
|
+
// Look for chat messages in various possible locations
|
|
298
|
+
const chatMessages = document.querySelectorAll('[class*="message"], [class*="chat"], [class*="conversation"]');
|
|
299
|
+
const messages = [];
|
|
300
|
+
|
|
301
|
+
chatMessages.forEach(msg => {
|
|
302
|
+
const text = msg.textContent || msg.innerText;
|
|
303
|
+
if (text && text.trim()) {
|
|
304
|
+
messages.push(text.trim());
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
return messages.join('\\n\\n');
|
|
309
|
+
} catch (error) {
|
|
310
|
+
return 'Error reading chat: ' + error.message;
|
|
311
|
+
}
|
|
312
|
+
})()`;
|
|
313
|
+
|
|
314
|
+
const { result } = await Runtime.evaluate({ expression, returnByValue: true });
|
|
315
|
+
return result.value || '[]';
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.error(`Error reading chat from ${ide}:`, error);
|
|
318
|
+
return `Error: ${error.message}`;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
// Claude Code CLI Manager - handles Claude Code CLI execution
|
|
2
|
+
const { execSync, spawn } = require('child_process');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
// Helper function for timestamps
|
|
8
|
+
function getTimestamp() {
|
|
9
|
+
const now = new Date();
|
|
10
|
+
return now.toLocaleTimeString('en-US', {
|
|
11
|
+
hour: '2-digit',
|
|
12
|
+
minute: '2-digit',
|
|
13
|
+
hour12: false
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class ClaudeCodeCLIManager {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.logger = console;
|
|
20
|
+
this.runningProcesses = [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Check if Claude Code CLI is installed
|
|
25
|
+
*/
|
|
26
|
+
isInstalled() {
|
|
27
|
+
try {
|
|
28
|
+
execSync('which claude', { stdio: 'pipe' });
|
|
29
|
+
return true;
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get Claude Code CLI version
|
|
37
|
+
*/
|
|
38
|
+
getVersion() {
|
|
39
|
+
try {
|
|
40
|
+
const version = execSync('claude --version', { encoding: 'utf8', stdio: 'pipe' });
|
|
41
|
+
return version.trim();
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Install Claude Code CLI
|
|
49
|
+
* @returns {Promise<{success: boolean, error?: string, suggestions?: string[]}>}
|
|
50
|
+
*/
|
|
51
|
+
async install() {
|
|
52
|
+
try {
|
|
53
|
+
console.log('\n📦 Installing Claude Code CLI...\n');
|
|
54
|
+
|
|
55
|
+
// Check if npm is available
|
|
56
|
+
try {
|
|
57
|
+
execSync('which npm', { stdio: 'pipe' });
|
|
58
|
+
} catch {
|
|
59
|
+
return {
|
|
60
|
+
success: false,
|
|
61
|
+
error: 'npm is not installed',
|
|
62
|
+
suggestions: [
|
|
63
|
+
'Install Node.js and npm first:',
|
|
64
|
+
' macOS: brew install node',
|
|
65
|
+
' Linux: sudo apt-get install nodejs npm',
|
|
66
|
+
' Windows: Download from https://nodejs.org/'
|
|
67
|
+
]
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Install Claude Code globally
|
|
72
|
+
console.log('Running: npm install -g @anthropic-ai/claude-code');
|
|
73
|
+
execSync('npm install -g @anthropic-ai/claude-code', {
|
|
74
|
+
stdio: 'inherit',
|
|
75
|
+
encoding: 'utf8'
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Verify installation
|
|
79
|
+
if (this.isInstalled()) {
|
|
80
|
+
const version = this.getVersion();
|
|
81
|
+
console.log(`\n✓ Claude Code CLI installed successfully: ${version}\n`);
|
|
82
|
+
return { success: true };
|
|
83
|
+
} else {
|
|
84
|
+
return {
|
|
85
|
+
success: false,
|
|
86
|
+
error: 'Installation completed but claude command not found',
|
|
87
|
+
suggestions: [
|
|
88
|
+
'Try running: npm install -g @anthropic-ai/claude-code',
|
|
89
|
+
'Make sure your npm global bin directory is in PATH'
|
|
90
|
+
]
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return {
|
|
95
|
+
success: false,
|
|
96
|
+
error: error.message,
|
|
97
|
+
suggestions: [
|
|
98
|
+
'Try installing manually: npm install -g @anthropic-ai/claude-code',
|
|
99
|
+
'Check if you have permission to install global npm packages',
|
|
100
|
+
'You may need to use: sudo npm install -g @anthropic-ai/claude-code'
|
|
101
|
+
]
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Kill all running Claude Code processes
|
|
108
|
+
*/
|
|
109
|
+
killAllProcesses() {
|
|
110
|
+
for (const proc of this.runningProcesses) {
|
|
111
|
+
try {
|
|
112
|
+
if (!proc.killed) {
|
|
113
|
+
proc.kill('SIGTERM');
|
|
114
|
+
setTimeout(() => {
|
|
115
|
+
if (!proc.killed) {
|
|
116
|
+
proc.kill('SIGKILL');
|
|
117
|
+
}
|
|
118
|
+
}, 1000);
|
|
119
|
+
}
|
|
120
|
+
} catch (err) {
|
|
121
|
+
// Ignore errors
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
this.runningProcesses = [];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Send a prompt to Claude Code and wait for completion
|
|
129
|
+
* @param {string} text - The message/prompt to send
|
|
130
|
+
* @param {string} cwd - Path to the repository (working directory)
|
|
131
|
+
* @param {Object} options - Additional options
|
|
132
|
+
* @param {Function} options.onOutput - Optional callback for output
|
|
133
|
+
* @param {Function} options.onError - Optional callback for errors
|
|
134
|
+
* @param {number} options.timeoutMs - Timeout in milliseconds (default 300000 = 5 minutes)
|
|
135
|
+
* @returns {Promise<{success: boolean, output: string, error?: string}>}
|
|
136
|
+
*/
|
|
137
|
+
async sendText(text, cwd = process.cwd(), options = {}) {
|
|
138
|
+
if (!this.isInstalled()) {
|
|
139
|
+
return {
|
|
140
|
+
success: false,
|
|
141
|
+
error: 'Claude Code CLI is not installed. Run install() first.',
|
|
142
|
+
needsInstall: true
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const { onOutput, onError, timeoutMs = 300000 } = options;
|
|
148
|
+
|
|
149
|
+
return new Promise((resolve) => {
|
|
150
|
+
const startTime = Date.now();
|
|
151
|
+
let stdout = '';
|
|
152
|
+
let stderr = '';
|
|
153
|
+
let completed = false;
|
|
154
|
+
let timeoutKilled = false;
|
|
155
|
+
let lastOutputTime = Date.now();
|
|
156
|
+
|
|
157
|
+
// Spawn Claude Code in the repository directory
|
|
158
|
+
const proc = spawn('claude', ['code'], {
|
|
159
|
+
cwd: cwd,
|
|
160
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
161
|
+
env: { ...process.env }
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
this.runningProcesses.push(proc);
|
|
165
|
+
|
|
166
|
+
// Status update interval - show progress every 10 seconds if no output
|
|
167
|
+
const statusInterval = setInterval(() => {
|
|
168
|
+
const timeSinceOutput = (Date.now() - lastOutputTime) / 1000;
|
|
169
|
+
if (timeSinceOutput > 10) {
|
|
170
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(0);
|
|
171
|
+
const chalk = require('chalk');
|
|
172
|
+
const timestamp = getTimestamp();
|
|
173
|
+
console.log(chalk.yellow(`[${timestamp}] [CLAUDE] Still waiting... (${elapsed}s elapsed, ${timeSinceOutput.toFixed(0)}s since last output)`));
|
|
174
|
+
}
|
|
175
|
+
}, 10000);
|
|
176
|
+
|
|
177
|
+
// Hard timeout - kill process if it runs too long
|
|
178
|
+
const hardTimeout = setTimeout(() => {
|
|
179
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(0);
|
|
180
|
+
const chalk = require('chalk');
|
|
181
|
+
const timestamp = getTimestamp();
|
|
182
|
+
console.log(chalk.red(`\n[${timestamp}] ⏰ TIMEOUT: Claude Code process exceeded ${timeoutMs/1000}s limit (ran for ${elapsed}s)`));
|
|
183
|
+
console.log(chalk.red(`[${timestamp}] Killing Claude Code process to prevent hanging...`));
|
|
184
|
+
timeoutKilled = true;
|
|
185
|
+
completed = true;
|
|
186
|
+
clearInterval(statusInterval);
|
|
187
|
+
proc.kill('SIGTERM');
|
|
188
|
+
setTimeout(() => proc.kill('SIGKILL'), 2000); // Force kill after 2s
|
|
189
|
+
}, timeoutMs);
|
|
190
|
+
|
|
191
|
+
// Handle stdout
|
|
192
|
+
proc.stdout.on('data', (chunk) => {
|
|
193
|
+
const chunkText = chunk.toString();
|
|
194
|
+
stdout += chunkText;
|
|
195
|
+
lastOutputTime = Date.now();
|
|
196
|
+
|
|
197
|
+
// Filter and show useful output
|
|
198
|
+
const lines = chunkText.split('\n');
|
|
199
|
+
for (const line of lines) {
|
|
200
|
+
const trimmed = line.trim();
|
|
201
|
+
if (!trimmed) continue;
|
|
202
|
+
|
|
203
|
+
// Call output callback if provided
|
|
204
|
+
if (onOutput) {
|
|
205
|
+
onOutput(line + '\n');
|
|
206
|
+
} else {
|
|
207
|
+
// Fallback: write directly to stdout
|
|
208
|
+
try {
|
|
209
|
+
const chalk = require('chalk');
|
|
210
|
+
process.stdout.write(chalk.gray(line + '\n'));
|
|
211
|
+
} catch {
|
|
212
|
+
process.stdout.write(line + '\n');
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Handle stderr
|
|
219
|
+
proc.stderr.on('data', (chunk) => {
|
|
220
|
+
const chunkText = chunk.toString();
|
|
221
|
+
stderr += chunkText;
|
|
222
|
+
|
|
223
|
+
// Only show actual errors
|
|
224
|
+
if (chunkText.includes('Error:') || chunkText.includes('Failed:') || chunkText.includes('error')) {
|
|
225
|
+
if (onError) {
|
|
226
|
+
onError(chunkText.trim());
|
|
227
|
+
} else {
|
|
228
|
+
this.logger.error('[Claude Code Error]', chunkText.trim());
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Send the message and close stdin
|
|
234
|
+
proc.stdin.write(text + '\n');
|
|
235
|
+
proc.stdin.end();
|
|
236
|
+
|
|
237
|
+
// Handle process exit
|
|
238
|
+
proc.on('close', (code) => {
|
|
239
|
+
// Remove process from tracking array
|
|
240
|
+
const index = this.runningProcesses.indexOf(proc);
|
|
241
|
+
if (index > -1) {
|
|
242
|
+
this.runningProcesses.splice(index, 1);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
clearInterval(statusInterval);
|
|
246
|
+
clearTimeout(hardTimeout);
|
|
247
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
248
|
+
const chalk = require('chalk');
|
|
249
|
+
const timestamp = getTimestamp();
|
|
250
|
+
|
|
251
|
+
if (timeoutKilled) {
|
|
252
|
+
console.log(chalk.red(`[${timestamp}] [CLAUDE] Process killed due to timeout after ${elapsed}s`));
|
|
253
|
+
resolve({
|
|
254
|
+
success: false,
|
|
255
|
+
output: stdout,
|
|
256
|
+
error: 'Timeout: Process exceeded maximum execution time',
|
|
257
|
+
exitCode: -1,
|
|
258
|
+
timeout: true
|
|
259
|
+
});
|
|
260
|
+
} else if (code === 0) {
|
|
261
|
+
console.log(chalk.gray(`[${timestamp}] [CLAUDE] Process completed after ${elapsed}s with code: ${code}`));
|
|
262
|
+
resolve({
|
|
263
|
+
success: true,
|
|
264
|
+
output: stdout,
|
|
265
|
+
stderr: stderr,
|
|
266
|
+
exitCode: code
|
|
267
|
+
});
|
|
268
|
+
} else {
|
|
269
|
+
console.log(chalk.gray(`[${timestamp}] [CLAUDE] Process closed after ${elapsed}s with code: ${code}`));
|
|
270
|
+
resolve({
|
|
271
|
+
success: false,
|
|
272
|
+
output: stdout,
|
|
273
|
+
error: stderr || `Process exited with code ${code}`,
|
|
274
|
+
exitCode: code
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
proc.on('error', (error) => {
|
|
280
|
+
clearInterval(statusInterval);
|
|
281
|
+
clearTimeout(hardTimeout);
|
|
282
|
+
completed = true;
|
|
283
|
+
const timestamp = getTimestamp();
|
|
284
|
+
console.log(`\n[${timestamp}] ✗ Claude Code error: ${error.message}`);
|
|
285
|
+
resolve({
|
|
286
|
+
success: false,
|
|
287
|
+
error: error.message,
|
|
288
|
+
exitCode: -1
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
} catch (error) {
|
|
293
|
+
return {
|
|
294
|
+
success: false,
|
|
295
|
+
error: error.message
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
module.exports = ClaudeCodeCLIManager;
|