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,2803 @@
|
|
|
1
|
+
// @vibecodingmachine/core - AppleScript Manager
|
|
2
|
+
// Handles AppleScript-based interactions with IDEs that don't support CDP
|
|
3
|
+
// Now supports both macOS (AppleScript) and Windows (PowerShell automation)
|
|
4
|
+
|
|
5
|
+
const { execSync } = require('child_process');
|
|
6
|
+
const { writeFileSync, unlinkSync } = require('fs');
|
|
7
|
+
const { join } = require('path');
|
|
8
|
+
const { tmpdir } = require('os');
|
|
9
|
+
|
|
10
|
+
// Import Windows automation manager for Windows platform
|
|
11
|
+
let WindowsAutomationManager;
|
|
12
|
+
if (process.platform === 'win32') {
|
|
13
|
+
WindowsAutomationManager = require('./windows-automation-manager');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const AppleScriptUtils = require('./applescript-utils');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* AppleScript Manager for IDE interactions
|
|
20
|
+
* Handles AppleScript-based text sending and response reading for IDEs like Cursor and Windsurf
|
|
21
|
+
*/
|
|
22
|
+
class AppleScriptManager {
|
|
23
|
+
constructor() {
|
|
24
|
+
this.logger = console;
|
|
25
|
+
this.platform = process.platform;
|
|
26
|
+
this.appleScriptUtils = new AppleScriptUtils();
|
|
27
|
+
|
|
28
|
+
if (this.platform === 'win32' && WindowsAutomationManager) {
|
|
29
|
+
this.windowsManager = new WindowsAutomationManager();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Deterministically send text to Terminal window 1 tab 1 and verify via history snippet
|
|
35
|
+
* @param {string} text
|
|
36
|
+
* @returns {Promise<Object>} Result with success and note
|
|
37
|
+
*/
|
|
38
|
+
async sendDeterministicToTerminalWindow1(text) {
|
|
39
|
+
// CRITICAL: Validate text parameter
|
|
40
|
+
if (!text || typeof text !== 'string') {
|
|
41
|
+
this.logger.error(`❌ [Deterministic] Invalid text parameter: type=${typeof text}, value=${text}`);
|
|
42
|
+
return {
|
|
43
|
+
success: false,
|
|
44
|
+
error: `Invalid text parameter: received ${typeof text} instead of string`,
|
|
45
|
+
debug: { textType: typeof text, textValue: text }
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
// First, read recent history to detect interactive prompts (like theme selection)
|
|
51
|
+
const readHistoryScript = `
|
|
52
|
+
tell application "Terminal"
|
|
53
|
+
try
|
|
54
|
+
set fullHistory to history of tab 1 of window 1 as text
|
|
55
|
+
if (length of fullHistory) > 600 then
|
|
56
|
+
set histSnippet to (text -600 thru -1 of fullHistory)
|
|
57
|
+
else
|
|
58
|
+
set histSnippet to fullHistory
|
|
59
|
+
end if
|
|
60
|
+
return histSnippet
|
|
61
|
+
on error
|
|
62
|
+
return "HIST_READ_ERROR"
|
|
63
|
+
end try
|
|
64
|
+
end tell
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
const tmpRead = join(tmpdir(), `claude_read_${Date.now()}.scpt`);
|
|
68
|
+
writeFileSync(tmpRead, readHistoryScript, 'utf8');
|
|
69
|
+
let historyOut = '';
|
|
70
|
+
try {
|
|
71
|
+
historyOut = execSync(`osascript "${tmpRead}"`, { encoding: 'utf8', timeout: 10000, stdio: 'pipe' }).trim();
|
|
72
|
+
} catch (rerr) {
|
|
73
|
+
this.logger.log('⚠️ Deterministic: failed to read history before send:', rerr.message || rerr);
|
|
74
|
+
historyOut = '';
|
|
75
|
+
} finally { try { unlinkSync(tmpRead); } catch (e) {} }
|
|
76
|
+
|
|
77
|
+
this.logger.log('🔎 Deterministic: recent history snippet length:', historyOut.length);
|
|
78
|
+
|
|
79
|
+
// Detect interactive prompt patterns that commonly appear on first run (e.g., theme selection)
|
|
80
|
+
const interactivePatterns = [
|
|
81
|
+
'Choose the text style',
|
|
82
|
+
'Change this later, run /theme',
|
|
83
|
+
'1. Dark mode',
|
|
84
|
+
'2. Light mode',
|
|
85
|
+
'Choose the text style that looks best',
|
|
86
|
+
'To change this later, run /theme'
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
const hasInteractivePrompt = interactivePatterns.some(p => historyOut.includes(p));
|
|
90
|
+
this.logger.log('🔎 Deterministic: interactive prompt detected=', hasInteractivePrompt);
|
|
91
|
+
|
|
92
|
+
const directSendScript = `
|
|
93
|
+
tell application "Terminal"
|
|
94
|
+
activate
|
|
95
|
+
try
|
|
96
|
+
set selected tab of window 1 to tab 1 of window 1
|
|
97
|
+
delay 0.5
|
|
98
|
+
end try
|
|
99
|
+
end tell
|
|
100
|
+
|
|
101
|
+
tell application "System Events"
|
|
102
|
+
tell process "Terminal"
|
|
103
|
+
set frontmost to true
|
|
104
|
+
delay 0.5
|
|
105
|
+
${hasInteractivePrompt ? `keystroke return\n delay 0.8` : ''}
|
|
106
|
+
keystroke "${text.replace(/"/g, '\\"')}"
|
|
107
|
+
delay 1.0
|
|
108
|
+
keystroke return
|
|
109
|
+
delay 0.3
|
|
110
|
+
end tell
|
|
111
|
+
end tell
|
|
112
|
+
|
|
113
|
+
tell application "Terminal"
|
|
114
|
+
try
|
|
115
|
+
set fullHistory to history of tab 1 of window 1 as text
|
|
116
|
+
if (length of fullHistory) > 300 then
|
|
117
|
+
set histSnippet to (text -300 thru -1 of fullHistory)
|
|
118
|
+
else
|
|
119
|
+
set histSnippet to fullHistory
|
|
120
|
+
end if
|
|
121
|
+
return "SENT_TO:W1_T1|HIST:" & histSnippet
|
|
122
|
+
on error
|
|
123
|
+
return "SENT_TO:W1_T1|HIST:ERROR"
|
|
124
|
+
end try
|
|
125
|
+
end tell
|
|
126
|
+
`;
|
|
127
|
+
|
|
128
|
+
const tempFile = join(tmpdir(), `claude_direct_${Date.now()}.scpt`);
|
|
129
|
+
writeFileSync(tempFile, directSendScript, 'utf8');
|
|
130
|
+
const out = execSync(`osascript "${tempFile}"`, { encoding: 'utf8', timeout: 30000, stdio: 'pipe' }).trim();
|
|
131
|
+
try { unlinkSync(tempFile); } catch (e) {}
|
|
132
|
+
|
|
133
|
+
this.logger.log('🔁 Deterministic direct send output:', out);
|
|
134
|
+
const verified = out.indexOf('|HIST:') !== -1 && out.split('|HIST:')[1].includes(text);
|
|
135
|
+
return { success: verified, note: out };
|
|
136
|
+
} catch (err) {
|
|
137
|
+
this.logger.log('❌ Deterministic direct send failed:', err.message || err);
|
|
138
|
+
return { success: false, error: err.message || String(err) };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Open Cursor IDE with optional repository path and remote debugging enabled
|
|
144
|
+
* @param {string} repoPath - Optional repository path to open
|
|
145
|
+
* @returns {Promise<Object>} Result object with success status and details
|
|
146
|
+
*/
|
|
147
|
+
async openCursor(repoPath = null) {
|
|
148
|
+
// Handle Windows platform
|
|
149
|
+
if (this.platform === 'win32' && this.windowsManager) {
|
|
150
|
+
return await this.windowsManager.openCursor(repoPath);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Handle macOS platform (existing AppleScript logic)
|
|
154
|
+
try {
|
|
155
|
+
this.logger.log('Opening Cursor with remote debugging enabled...');
|
|
156
|
+
|
|
157
|
+
// Launch Cursor with remote debugging enabled
|
|
158
|
+
let command = 'open -a "Cursor" --args --remote-debugging-port=9225';
|
|
159
|
+
if (repoPath) {
|
|
160
|
+
command = `open -a "Cursor" "${repoPath}" --args --remote-debugging-port=9225`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
execSync(command, { stdio: 'pipe' });
|
|
164
|
+
|
|
165
|
+
// Wait for Cursor to start and bind the debugging port
|
|
166
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
167
|
+
|
|
168
|
+
this.logger.log('Cursor opened successfully with remote debugging on port 9225');
|
|
169
|
+
return {
|
|
170
|
+
success: true,
|
|
171
|
+
message: repoPath ? `Cursor opened with repository: ${repoPath} and remote debugging` : 'Cursor opened successfully with remote debugging',
|
|
172
|
+
method: 'applescript',
|
|
173
|
+
debugPort: 9225
|
|
174
|
+
};
|
|
175
|
+
} catch (error) {
|
|
176
|
+
this.logger.log('Error opening Cursor:', error.message);
|
|
177
|
+
return {
|
|
178
|
+
success: false,
|
|
179
|
+
error: error.message,
|
|
180
|
+
method: 'applescript'
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Open Gemini (in VS Code)
|
|
187
|
+
* @param {string} repoPath - Optional repository path to open
|
|
188
|
+
* @returns {Promise<Object>} Result object with success status and details
|
|
189
|
+
*/
|
|
190
|
+
async openGemini(repoPath = null) {
|
|
191
|
+
this.logger.log('Opening Gemini, which runs in VS Code...');
|
|
192
|
+
return await this.openVSCode(repoPath);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Open Claude in a new terminal or bring existing instance to foreground
|
|
197
|
+
* @param {string} repoPath - Optional repository path to open (will cd to this directory before starting Claude)
|
|
198
|
+
* @returns {Promise<Object>} Result object with success status and details
|
|
199
|
+
*/
|
|
200
|
+
async openClaude(repoPath = null) {
|
|
201
|
+
try {
|
|
202
|
+
this.logger.log('Opening Claude in terminal or bringing to foreground...');
|
|
203
|
+
|
|
204
|
+
// First, check if any Claude process is running using a simple shell command
|
|
205
|
+
try {
|
|
206
|
+
const claudeProcessCheck = execSync('ps aux | grep -i "claude" | grep -v grep | grep -v "Claude.app" | wc -l', { encoding: 'utf8' }).trim();
|
|
207
|
+
const claudeCount = parseInt(claudeProcessCheck);
|
|
208
|
+
this.logger.log(`🔍 [Claude] Found ${claudeCount} Claude processes running`);
|
|
209
|
+
|
|
210
|
+
if (claudeCount === 0) {
|
|
211
|
+
this.logger.log('No Claude process found, will create new terminal');
|
|
212
|
+
throw new Error('No Claude process running');
|
|
213
|
+
}
|
|
214
|
+
} catch (processCheckError) {
|
|
215
|
+
this.logger.log('No Claude process detected, will create new terminal');
|
|
216
|
+
// Jump to creating new terminal
|
|
217
|
+
const escapedPath = repoPath ? repoPath.replace(/'/g, "'\\''") : '';
|
|
218
|
+
const cdCommand = repoPath ? `cd '${escapedPath}' && ` : '';
|
|
219
|
+
const openClaudeScript = `
|
|
220
|
+
tell application "Terminal"
|
|
221
|
+
activate
|
|
222
|
+
set newWindow to do script "echo 'Starting Claude Code...' && ${cdCommand}claude --dangerously-skip-permissions"
|
|
223
|
+
delay 3
|
|
224
|
+
end tell
|
|
225
|
+
`;
|
|
226
|
+
|
|
227
|
+
const tempFile = join(tmpdir(), `claude_open_${Date.now()}.scpt`);
|
|
228
|
+
try {
|
|
229
|
+
writeFileSync(tempFile, openClaudeScript, 'utf8');
|
|
230
|
+
execSync(`osascript "${tempFile}"`, { stdio: 'pipe' });
|
|
231
|
+
|
|
232
|
+
this.logger.log('Claude opened in new terminal successfully');
|
|
233
|
+
return {
|
|
234
|
+
success: true,
|
|
235
|
+
message: repoPath ? `Claude opened in terminal with context: ${repoPath}` : 'Claude opened in new terminal',
|
|
236
|
+
method: 'applescript',
|
|
237
|
+
action: 'open'
|
|
238
|
+
};
|
|
239
|
+
} finally {
|
|
240
|
+
try {
|
|
241
|
+
unlinkSync(tempFile);
|
|
242
|
+
} catch (cleanupError) {
|
|
243
|
+
this.logger.log(`⚠️ Failed to cleanup temp file: ${cleanupError.message}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// If we get here, Claude process exists - find the specific Claude terminal window
|
|
249
|
+
try {
|
|
250
|
+
this.logger.log('🔍 [Claude] Claude process detected, searching for Claude terminal window...');
|
|
251
|
+
const escapedRepoPath = repoPath ? repoPath.replace(/'/g, "'\\''") : '';
|
|
252
|
+
const repoPathCheck = repoPath ? `and historyText contains "${escapedRepoPath}"` : '';
|
|
253
|
+
const findClaudeWindowScript = `
|
|
254
|
+
tell application "Terminal"
|
|
255
|
+
activate
|
|
256
|
+
delay 0.5
|
|
257
|
+
|
|
258
|
+
set claudeWindow to null
|
|
259
|
+
set windowCount to count of windows
|
|
260
|
+
|
|
261
|
+
-- Search through all terminal windows to find the one running Claude IN THE CORRECT DIRECTORY
|
|
262
|
+
repeat with i from 1 to windowCount
|
|
263
|
+
try
|
|
264
|
+
set currentWindow to window i
|
|
265
|
+
set currentTab to selected tab of currentWindow
|
|
266
|
+
set historyText to history of currentTab as text
|
|
267
|
+
|
|
268
|
+
-- Check for Claude AND optionally the correct repo path to avoid wrong terminal
|
|
269
|
+
-- CRITICAL FIX: Must check BOTH Claude process AND repo path to avoid wrong terminal
|
|
270
|
+
if (historyText contains "Claude Code" or historyText contains "claude --dangerously-skip-permissions") ${repoPathCheck} then
|
|
271
|
+
set claudeWindow to currentWindow
|
|
272
|
+
exit repeat
|
|
273
|
+
end if
|
|
274
|
+
on error
|
|
275
|
+
-- Continue to next window
|
|
276
|
+
end try
|
|
277
|
+
end repeat
|
|
278
|
+
|
|
279
|
+
if claudeWindow is not null then
|
|
280
|
+
-- Found Claude window - bring it to front
|
|
281
|
+
set index of claudeWindow to 1
|
|
282
|
+
delay 0.5
|
|
283
|
+
return "claude-window-found"
|
|
284
|
+
else
|
|
285
|
+
return "claude-window-not-found"
|
|
286
|
+
end if
|
|
287
|
+
end tell
|
|
288
|
+
`;
|
|
289
|
+
|
|
290
|
+
const tempFile = join(tmpdir(), `claude_find_${Date.now()}.scpt`);
|
|
291
|
+
try {
|
|
292
|
+
writeFileSync(tempFile, findClaudeWindowScript, 'utf8');
|
|
293
|
+
const result = execSync(`osascript "${tempFile}"`, { stdio: 'pipe', encoding: 'utf8' }).trim();
|
|
294
|
+
|
|
295
|
+
if (result === 'claude-window-found') {
|
|
296
|
+
this.logger.log('✅ [Claude] Found and focused Claude terminal window');
|
|
297
|
+
return {
|
|
298
|
+
success: true,
|
|
299
|
+
message: 'Claude terminal window found and focused',
|
|
300
|
+
method: 'applescript',
|
|
301
|
+
action: 'focus'
|
|
302
|
+
};
|
|
303
|
+
} else {
|
|
304
|
+
this.logger.log('⚠️ [Claude] Claude window not found despite process running, will create new one');
|
|
305
|
+
}
|
|
306
|
+
} finally {
|
|
307
|
+
try {
|
|
308
|
+
unlinkSync(tempFile);
|
|
309
|
+
} catch (cleanupError) {
|
|
310
|
+
this.logger.log(`⚠️ Failed to cleanup temp file: ${cleanupError.message}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
} catch (focusError) {
|
|
314
|
+
this.logger.log('⚠️ [Claude] Error finding Claude window, will create new one:', focusError.message);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// No existing Claude found, open a new terminal with Claude
|
|
318
|
+
// Escape the path properly for AppleScript
|
|
319
|
+
const escapedPath = repoPath ? repoPath.replace(/'/g, "'\\''") : '';
|
|
320
|
+
const cdCommand = repoPath ? `cd '${escapedPath}' && ` : '';
|
|
321
|
+
const openClaudeScript = `
|
|
322
|
+
tell application "Terminal"
|
|
323
|
+
activate
|
|
324
|
+
set newWindow to do script "echo 'Starting Claude Code...' && ${cdCommand}claude --dangerously-skip-permissions"
|
|
325
|
+
delay 3
|
|
326
|
+
end tell
|
|
327
|
+
`;
|
|
328
|
+
|
|
329
|
+
// Use file-based execution to avoid quote escaping issues
|
|
330
|
+
const tempFile = join(tmpdir(), `claude_open_${Date.now()}.scpt`);
|
|
331
|
+
try {
|
|
332
|
+
writeFileSync(tempFile, openClaudeScript, 'utf8');
|
|
333
|
+
execSync(`osascript "${tempFile}"`, { stdio: 'pipe' });
|
|
334
|
+
|
|
335
|
+
this.logger.log('Claude opened in new terminal successfully');
|
|
336
|
+
return {
|
|
337
|
+
success: true,
|
|
338
|
+
message: repoPath ? `Claude opened in terminal with context: ${repoPath}` : 'Claude opened in new terminal',
|
|
339
|
+
method: 'applescript',
|
|
340
|
+
action: 'open'
|
|
341
|
+
};
|
|
342
|
+
} finally {
|
|
343
|
+
try {
|
|
344
|
+
unlinkSync(tempFile);
|
|
345
|
+
} catch (cleanupError) {
|
|
346
|
+
this.logger.log(`⚠️ Failed to cleanup temp file: ${cleanupError.message}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
} catch (error) {
|
|
350
|
+
this.logger.log('Error opening Claude:', error.message);
|
|
351
|
+
return {
|
|
352
|
+
success: false,
|
|
353
|
+
error: error.message,
|
|
354
|
+
method: 'applescript'
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Open VS Code with optional repository path
|
|
361
|
+
* @param {string} repoPath - Optional repository path to open
|
|
362
|
+
* @returns {Promise<Object>} Result object with success status and details
|
|
363
|
+
*/
|
|
364
|
+
async openVSCode(repoPath = null) {
|
|
365
|
+
// Handle Windows platform
|
|
366
|
+
if (this.platform === 'win32' && this.windowsManager) {
|
|
367
|
+
// Assuming a similar method exists in the windows manager
|
|
368
|
+
return await this.windowsManager.openVSCode(repoPath);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Handle macOS platform
|
|
372
|
+
try {
|
|
373
|
+
this.logger.log('Opening VS Code...');
|
|
374
|
+
|
|
375
|
+
let command = 'open -a "Visual Studio Code"';
|
|
376
|
+
if (repoPath) {
|
|
377
|
+
command += ` "${repoPath}"`;
|
|
378
|
+
this.logger.log(`Opening VS Code with repository: ${repoPath}`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
execSync(command, { stdio: 'pipe' });
|
|
382
|
+
|
|
383
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
384
|
+
|
|
385
|
+
this.logger.log('VS Code opened successfully');
|
|
386
|
+
return { success: true, message: `VS Code opened with repository: ${repoPath}`, method: 'applescript' };
|
|
387
|
+
} catch (error) {
|
|
388
|
+
this.logger.log('Error opening VS Code:', error.message);
|
|
389
|
+
return { success: false, error: error.message, method: 'applescript' };
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Open Windsurf IDE with optional repository path
|
|
396
|
+
* @param {string} repoPath - Optional repository path to open
|
|
397
|
+
* @returns {Promise<Object>} Result object with success status and details
|
|
398
|
+
*/
|
|
399
|
+
async openWindsurf(repoPath = null) {
|
|
400
|
+
try {
|
|
401
|
+
this.logger.log('Opening Windsurf...');
|
|
402
|
+
|
|
403
|
+
// Try to find Windsurf installation
|
|
404
|
+
const possiblePaths = [
|
|
405
|
+
'/Users/jesseolsen/.codeium/windsurf/bin/windsurf', // macOS - Codeium installation
|
|
406
|
+
'/Applications/Windsurf.app/Contents/MacOS/Windsurf', // macOS - App Store
|
|
407
|
+
'C:\\Users\\%USERNAME%\\AppData\\Local\\Programs\\Windsurf\\Windsurf.exe', // Windows
|
|
408
|
+
'/usr/local/bin/windsurf', // Linux
|
|
409
|
+
'/opt/windsurf/windsurf' // Linux alternative
|
|
410
|
+
];
|
|
411
|
+
|
|
412
|
+
let windsurfPath = possiblePaths.find(path => {
|
|
413
|
+
try {
|
|
414
|
+
const fs = require('fs');
|
|
415
|
+
fs.accessSync(path.replace('%USERNAME%', process.env.USERNAME || process.env.USER));
|
|
416
|
+
return true;
|
|
417
|
+
} catch {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
if (!windsurfPath) {
|
|
423
|
+
// Try using the 'windsurf' command directly
|
|
424
|
+
windsurfPath = 'windsurf';
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
let command;
|
|
428
|
+
if (windsurfPath.includes('Windsurf.app')) {
|
|
429
|
+
// macOS app bundle
|
|
430
|
+
command = `open -a "Windsurf"`;
|
|
431
|
+
if (repoPath) {
|
|
432
|
+
command += ` "${repoPath}"`;
|
|
433
|
+
}
|
|
434
|
+
} else {
|
|
435
|
+
// Direct executable - use --reuse-window flag to avoid hanging when Windsurf is already running
|
|
436
|
+
command = `"${windsurfPath}" --reuse-window`;
|
|
437
|
+
if (repoPath) {
|
|
438
|
+
command += ` "${repoPath}"`;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
this.logger.log(`Executing command: ${command}`);
|
|
443
|
+
|
|
444
|
+
// For Windsurf, we don't wait for the command to complete since it runs in background
|
|
445
|
+
if (windsurfPath.includes('windsurf')) {
|
|
446
|
+
// Use spawn to run in background
|
|
447
|
+
const { spawn } = require('child_process');
|
|
448
|
+
const windsurfProcess = spawn(windsurfPath, ['--reuse-window', repoPath], {
|
|
449
|
+
stdio: 'pipe',
|
|
450
|
+
detached: true
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// Don't wait for completion, just give it a moment to start
|
|
454
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
455
|
+
} else {
|
|
456
|
+
execSync(command, { stdio: 'pipe' });
|
|
457
|
+
// Wait a moment for Windsurf to start
|
|
458
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
this.logger.log('Windsurf opened successfully');
|
|
462
|
+
return {
|
|
463
|
+
success: true,
|
|
464
|
+
message: repoPath ? `Windsurf opened with repository: ${repoPath}` : 'Windsurf opened successfully',
|
|
465
|
+
method: 'applescript'
|
|
466
|
+
};
|
|
467
|
+
} catch (error) {
|
|
468
|
+
this.logger.log('Error opening Windsurf:', error.message);
|
|
469
|
+
return {
|
|
470
|
+
success: false,
|
|
471
|
+
error: error.message,
|
|
472
|
+
method: 'applescript'
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
async openAntigravity(repoPath = null) {
|
|
478
|
+
try {
|
|
479
|
+
this.logger.log('Opening Google Antigravity...');
|
|
480
|
+
|
|
481
|
+
// Google Antigravity app path
|
|
482
|
+
const antigravityPath = '/Applications/Antigravity.app';
|
|
483
|
+
|
|
484
|
+
// Check if Antigravity is installed
|
|
485
|
+
try {
|
|
486
|
+
const fs = require('fs');
|
|
487
|
+
fs.accessSync(antigravityPath);
|
|
488
|
+
} catch {
|
|
489
|
+
throw new Error('Google Antigravity is not installed at /Applications/Antigravity.app');
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
let command = `open -a "Antigravity"`;
|
|
493
|
+
if (repoPath) {
|
|
494
|
+
command += ` "${repoPath}"`;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
this.logger.log(`Executing command: ${command}`);
|
|
498
|
+
execSync(command, { stdio: 'pipe' });
|
|
499
|
+
|
|
500
|
+
// Wait a moment for Antigravity to start
|
|
501
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
502
|
+
|
|
503
|
+
this.logger.log('Google Antigravity opened successfully');
|
|
504
|
+
return {
|
|
505
|
+
success: true,
|
|
506
|
+
message: repoPath ? `Google Antigravity opened with repository: ${repoPath}` : 'Google Antigravity opened successfully',
|
|
507
|
+
method: 'applescript'
|
|
508
|
+
};
|
|
509
|
+
} catch (error) {
|
|
510
|
+
this.logger.log('Error opening Google Antigravity:', error.message);
|
|
511
|
+
return {
|
|
512
|
+
success: false,
|
|
513
|
+
error: error.message,
|
|
514
|
+
method: 'applescript'
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Handle Antigravity quota limit by automatically switching models
|
|
521
|
+
* @returns {Promise<{success: boolean, model?: string, error?: string}>}
|
|
522
|
+
*/
|
|
523
|
+
async handleAntigravityQuotaLimit() {
|
|
524
|
+
try {
|
|
525
|
+
this.logger.log('Attempting to handle Antigravity quota limit...');
|
|
526
|
+
|
|
527
|
+
const script = `
|
|
528
|
+
tell application "System Events"
|
|
529
|
+
tell process "Antigravity"
|
|
530
|
+
set frontmost to true
|
|
531
|
+
delay 0.8
|
|
532
|
+
|
|
533
|
+
-- Try to find and click "Select another model" button
|
|
534
|
+
try
|
|
535
|
+
set modelButtons to buttons of window 1 whose name contains "Select another model"
|
|
536
|
+
if (count of modelButtons) > 0 then
|
|
537
|
+
click item 1 of modelButtons
|
|
538
|
+
delay 1.5
|
|
539
|
+
|
|
540
|
+
-- Look for model dropdown/menu items
|
|
541
|
+
-- Try alternative models in order of preference
|
|
542
|
+
set modelNames to {"Gemini 3 Pro (Low)", "Claude Sonnet 4.5", "Claude Sonnet 4.5 (Thinking)", "GPT-OSS 120B (Medium)"}
|
|
543
|
+
|
|
544
|
+
repeat with modelName in modelNames
|
|
545
|
+
try
|
|
546
|
+
-- Try to find and click this model option
|
|
547
|
+
set modelItems to menu items of menu 1 of window 1 whose name is modelName
|
|
548
|
+
if (count of modelItems) > 0 then
|
|
549
|
+
click item 1 of modelItems
|
|
550
|
+
delay 0.8
|
|
551
|
+
|
|
552
|
+
-- Click "Accept all" or similar confirmation button
|
|
553
|
+
try
|
|
554
|
+
set acceptButtons to buttons of window 1 whose name contains "Accept"
|
|
555
|
+
if (count of acceptButtons) > 0 then
|
|
556
|
+
click item 1 of acceptButtons
|
|
557
|
+
delay 0.5
|
|
558
|
+
return "success:" & modelName
|
|
559
|
+
end if
|
|
560
|
+
end try
|
|
561
|
+
|
|
562
|
+
return "success:" & modelName
|
|
563
|
+
end if
|
|
564
|
+
end try
|
|
565
|
+
end repeat
|
|
566
|
+
|
|
567
|
+
return "error:no-models-available"
|
|
568
|
+
else
|
|
569
|
+
return "error:button-not-found"
|
|
570
|
+
end if
|
|
571
|
+
on error errMsg
|
|
572
|
+
return "error:" & errMsg
|
|
573
|
+
end try
|
|
574
|
+
end tell
|
|
575
|
+
end tell
|
|
576
|
+
`;
|
|
577
|
+
|
|
578
|
+
const result = execSync(`osascript -e '${script.replace(/'/g, "'\\''")}'`, {
|
|
579
|
+
encoding: 'utf8',
|
|
580
|
+
timeout: 15000
|
|
581
|
+
}).trim();
|
|
582
|
+
|
|
583
|
+
this.logger.log('AppleScript result:', result);
|
|
584
|
+
|
|
585
|
+
if (result.startsWith('success:')) {
|
|
586
|
+
const model = result.substring(8);
|
|
587
|
+
this.logger.log(`Successfully switched to model: ${model}`);
|
|
588
|
+
return { success: true, model };
|
|
589
|
+
} else {
|
|
590
|
+
const error = result.substring(6);
|
|
591
|
+
this.logger.log(`Failed to switch model: ${error}`);
|
|
592
|
+
return { success: false, error };
|
|
593
|
+
}
|
|
594
|
+
} catch (error) {
|
|
595
|
+
this.logger.log('Error handling Antigravity quota limit:', error.message);
|
|
596
|
+
return { success: false, error: error.message };
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Generic IDE opener - routes to specific IDE methods
|
|
602
|
+
* @param {string} ide - The IDE name ('cursor', 'windsurf', 'antigravity', 'vscode', etc.')
|
|
603
|
+
* @param {string} repoPath - Optional repository path to open
|
|
604
|
+
* @returns {Promise<Object>} Result object
|
|
605
|
+
*/
|
|
606
|
+
async openIDE(ide, repoPath = null) {
|
|
607
|
+
const ideLower = (ide || '').toLowerCase();
|
|
608
|
+
|
|
609
|
+
switch (ideLower) {
|
|
610
|
+
case 'cursor':
|
|
611
|
+
return await this.openCursor(repoPath);
|
|
612
|
+
case 'windsurf':
|
|
613
|
+
return await this.openWindsurf(repoPath);
|
|
614
|
+
case 'antigravity':
|
|
615
|
+
return await this.openAntigravity(repoPath);
|
|
616
|
+
case 'vscode':
|
|
617
|
+
return await this.openVSCode(repoPath);
|
|
618
|
+
case 'claude':
|
|
619
|
+
return await this.openClaude(repoPath);
|
|
620
|
+
case 'gemini':
|
|
621
|
+
return await this.openGemini(repoPath);
|
|
622
|
+
default:
|
|
623
|
+
return {
|
|
624
|
+
success: false,
|
|
625
|
+
error: `Unknown IDE: ${ide}. Supported: cursor, windsurf, antigravity, vscode, claude, gemini`
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Ensure we're not in an editor context before sending text
|
|
632
|
+
* @param {string} ide - The IDE name
|
|
633
|
+
* @returns {Promise<boolean>} True if safe to send text, false if in editor
|
|
634
|
+
*/
|
|
635
|
+
async ensureNotInEditor(ide) {
|
|
636
|
+
if (ide !== 'cursor') {
|
|
637
|
+
return true; // Only check for Cursor
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
try {
|
|
641
|
+
const safetyScript = `
|
|
642
|
+
tell application "System Events"
|
|
643
|
+
tell process "Cursor"
|
|
644
|
+
set frontmost to true
|
|
645
|
+
delay 0.5
|
|
646
|
+
|
|
647
|
+
-- Check if we're currently in a text editor
|
|
648
|
+
try
|
|
649
|
+
-- Try to get the focused element
|
|
650
|
+
set focusedElement to value of attribute "AXFocused" of window 1
|
|
651
|
+
if focusedElement is not missing value then
|
|
652
|
+
-- We're in an editor, need to escape
|
|
653
|
+
key code 53 -- Escape key to exit editor mode
|
|
654
|
+
delay 0.5
|
|
655
|
+
return "escaped_from_editor"
|
|
656
|
+
else
|
|
657
|
+
return "not_in_editor"
|
|
658
|
+
end if
|
|
659
|
+
on error
|
|
660
|
+
-- If we can't determine focus, assume we're safe
|
|
661
|
+
return "focus_check_failed_assuming_safe"
|
|
662
|
+
end try
|
|
663
|
+
end tell
|
|
664
|
+
end tell
|
|
665
|
+
`;
|
|
666
|
+
|
|
667
|
+
const tempFile = join(tmpdir(), `safety_check_${Date.now()}.scpt`);
|
|
668
|
+
try {
|
|
669
|
+
writeFileSync(tempFile, safetyScript, 'utf8');
|
|
670
|
+
const result = execSync(`osascript "${tempFile}"`, { stdio: 'pipe', encoding: 'utf8' });
|
|
671
|
+
this.logger.log(`🔒 [${ide}] Safety check result: ${result.trim()}`);
|
|
672
|
+
return true; // Always return true to proceed, but log the result
|
|
673
|
+
} finally {
|
|
674
|
+
try {
|
|
675
|
+
unlinkSync(tempFile);
|
|
676
|
+
} catch (cleanupError) {
|
|
677
|
+
this.logger.log(`⚠️ Failed to cleanup safety check temp file: ${cleanupError.message}`);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
} catch (error) {
|
|
681
|
+
this.logger.log(`⚠️ Safety check failed: ${error.message}`);
|
|
682
|
+
return true; // Proceed anyway if safety check fails
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Send text to VS Code using AppleScript
|
|
688
|
+
* @param {string} text - The text to send
|
|
689
|
+
* @returns {Promise<Object>} Result object with success status and details
|
|
690
|
+
*/
|
|
691
|
+
async sendTextToVSCode(text) {
|
|
692
|
+
const ideName = 'VS Code';
|
|
693
|
+
this.logger.log(`🚀 [${ideName}] Using dedicated sendTextToVSCode method with new chat session.`);
|
|
694
|
+
|
|
695
|
+
try {
|
|
696
|
+
// Get current extension from manager instance
|
|
697
|
+
const extension = this.currentExtension || 'gemini-code'; // Default to gemini-code
|
|
698
|
+
let commandText = 'Gemini Code Assist: Focus on Chat View'; // default for gemini
|
|
699
|
+
|
|
700
|
+
if (extension === 'amazon-q') {
|
|
701
|
+
commandText = 'Amazon Q: Open Chat';
|
|
702
|
+
} else if (extension === 'github-copilot') {
|
|
703
|
+
commandText = 'GitHub Copilot Chat: Open Chat';
|
|
704
|
+
} else if (extension === 'gemini-code') {
|
|
705
|
+
commandText = 'Gemini Code Assist: Focus on Chat View';
|
|
706
|
+
} else if (extension === 'windsurf') {
|
|
707
|
+
commandText = 'Windsurf: Open Chat';
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const appleScript = `
|
|
711
|
+
tell application "System Events"
|
|
712
|
+
tell process "Visual Studio Code"
|
|
713
|
+
-- Check if the process is running before attempting to interact
|
|
714
|
+
try
|
|
715
|
+
set frontmost to true
|
|
716
|
+
delay 1.5
|
|
717
|
+
-- Step 1: Start new chat session first (only for Gemini)
|
|
718
|
+
${extension === 'gemini-code' ? `
|
|
719
|
+
key code 35 using {command down, shift down}
|
|
720
|
+
delay 1.0
|
|
721
|
+
keystroke "Gemini Code Assistant New Chat"
|
|
722
|
+
delay 1.5
|
|
723
|
+
key code 36
|
|
724
|
+
delay 2.0
|
|
725
|
+
` : ''}
|
|
726
|
+
|
|
727
|
+
-- Step 2: Open Command Palette to focus on chat view
|
|
728
|
+
key code 35 using {command down, shift down}
|
|
729
|
+
delay 1.0
|
|
730
|
+
|
|
731
|
+
-- Type to open the selected extension chat (increased delay for command processing)
|
|
732
|
+
keystroke "${commandText.replace(/"/g, '\\"')}"
|
|
733
|
+
delay 1.5
|
|
734
|
+
|
|
735
|
+
-- Press Enter (increased delay for chat window to fully open)
|
|
736
|
+
key code 36
|
|
737
|
+
delay 2.5
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
-- Type the message (increased delay for input field to be ready)
|
|
741
|
+
keystroke "${text.replace(/"/g, '\\"')}"
|
|
742
|
+
delay 1.0
|
|
743
|
+
|
|
744
|
+
-- Send with Cmd+Enter or Enter
|
|
745
|
+
key code 36 using {command down}
|
|
746
|
+
delay 0.5
|
|
747
|
+
key code 36
|
|
748
|
+
delay 1.5
|
|
749
|
+
on error
|
|
750
|
+
-- Process not running or not accessible
|
|
751
|
+
error "Process 'Visual Studio Code' is not running or not accessible. VibeCodingMachine does not open IDEs - please open VS Code manually first."
|
|
752
|
+
end try
|
|
753
|
+
end tell
|
|
754
|
+
end tell
|
|
755
|
+
`;
|
|
756
|
+
|
|
757
|
+
const tempFile = join(tmpdir(), `vscode_script_${Date.now()}.scpt`);
|
|
758
|
+
writeFileSync(tempFile, appleScript, 'utf8');
|
|
759
|
+
execSync(`osascript "${tempFile}"`, { stdio: 'pipe' });
|
|
760
|
+
unlinkSync(tempFile);
|
|
761
|
+
|
|
762
|
+
return { success: true, method: 'applescript', message: `Message sent to ${ideName}: ${text}` };
|
|
763
|
+
} catch (error) {
|
|
764
|
+
return { success: false, error: error.message, method: 'applescript' };
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Send text to an IDE using AppleScript
|
|
770
|
+
* @param {string} text - The text to send
|
|
771
|
+
* @param {string} ide - The IDE name ('cursor', 'windsurf', 'claude')
|
|
772
|
+
* @param {string} repoPath - The repository path (optional, used for Claude)
|
|
773
|
+
* @returns {Promise<Object>} Result object with success status and details
|
|
774
|
+
*/
|
|
775
|
+
async sendText(text, ide, repoPath = null) {
|
|
776
|
+
if (typeof text !== 'string') {
|
|
777
|
+
return {
|
|
778
|
+
success: false,
|
|
779
|
+
error: `Invalid text type: ${typeof text}. Expected string.`,
|
|
780
|
+
debug: { textType: typeof text, textValue: text }
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const ideName = ide === 'windsurf' ? 'Windsurf' : ide === 'cursor' ? 'Cursor' : ide === 'antigravity' ? 'Antigravity' : ide === 'claude' ? 'Claude' : ide === 'vscode' ? 'VS Code' : 'Unknown IDE';
|
|
785
|
+
|
|
786
|
+
this.logger.log(`🚀 [${ideName}] Starting text send on ${this.platform} platform`);
|
|
787
|
+
this.logger.log(`🚀 [${ideName}] Text to send: "${text}"`);
|
|
788
|
+
|
|
789
|
+
// Special handling for Windsurf extension within VS Code
|
|
790
|
+
if (ide === 'windsurf' && this.isRunningInVSCodeExtension()) {
|
|
791
|
+
return await this.sendTextToWindsurfExtension(text);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// Handle Windows platform
|
|
795
|
+
if (this.platform === 'win32') {
|
|
796
|
+
return await this.sendTextWindows(text, ide);
|
|
797
|
+
}
|
|
798
|
+
this.logger.log(`🚀 [${ideName}] Using precise element targeting - AVOIDING TERMINAL`);
|
|
799
|
+
|
|
800
|
+
// Safety check: Ensure we're not in an editor before sending text
|
|
801
|
+
await this.ensureNotInEditor(ide);
|
|
802
|
+
|
|
803
|
+
// Enhanced diagnostic for Cursor to understand UI structure and prevent file typing
|
|
804
|
+
if (ide === 'cursor') {
|
|
805
|
+
try {
|
|
806
|
+
const diagnosticScript = `
|
|
807
|
+
tell application "System Events"
|
|
808
|
+
tell process "Cursor"
|
|
809
|
+
set frontmost to true
|
|
810
|
+
delay 1
|
|
811
|
+
|
|
812
|
+
-- Check if we're currently in an editor text area
|
|
813
|
+
set inEditor to false
|
|
814
|
+
try
|
|
815
|
+
set activeElement to value of attribute "AXFocused" of window 1
|
|
816
|
+
if activeElement is not missing value then
|
|
817
|
+
set inEditor to true
|
|
818
|
+
end if
|
|
819
|
+
on error
|
|
820
|
+
-- Continue
|
|
821
|
+
end try
|
|
822
|
+
|
|
823
|
+
-- Log all text fields and their descriptions
|
|
824
|
+
set allTextFields to text field of window 1
|
|
825
|
+
set fieldCount to count of allTextFields
|
|
826
|
+
log "Found " & fieldCount & " text fields in Cursor"
|
|
827
|
+
|
|
828
|
+
repeat with i from 1 to fieldCount
|
|
829
|
+
try
|
|
830
|
+
set currentField to item i of allTextFields
|
|
831
|
+
set fieldDesc to description of currentField
|
|
832
|
+
log "Field " & i & ": " & fieldDesc
|
|
833
|
+
on error
|
|
834
|
+
log "Field " & i & ": No description available"
|
|
835
|
+
end try
|
|
836
|
+
end repeat
|
|
837
|
+
|
|
838
|
+
-- Log all groups and their structure
|
|
839
|
+
set allGroups to group of window 1
|
|
840
|
+
set groupCount to count of allGroups
|
|
841
|
+
log "Found " & groupCount & " groups in Cursor"
|
|
842
|
+
|
|
843
|
+
-- Log current focus state
|
|
844
|
+
log "Currently in editor: " & inEditor
|
|
845
|
+
end tell
|
|
846
|
+
end tell
|
|
847
|
+
`;
|
|
848
|
+
|
|
849
|
+
this.logger.log(`🔍 [${ideName}] Running enhanced diagnostic script to understand Cursor UI structure...`);
|
|
850
|
+
|
|
851
|
+
// Use file-based AppleScript execution to avoid shell escaping issues
|
|
852
|
+
const tempFile = join(tmpdir(), `cursor_diagnostic_${Date.now()}.scpt`);
|
|
853
|
+
try {
|
|
854
|
+
writeFileSync(tempFile, diagnosticScript, 'utf8');
|
|
855
|
+
const diagnosticResult = execSync(`osascript "${tempFile}"`, { stdio: 'pipe', encoding: 'utf8' });
|
|
856
|
+
this.logger.log(`✅ [${ideName}] Diagnostic completed: ${diagnosticResult.trim()}`);
|
|
857
|
+
} finally {
|
|
858
|
+
// Clean up temporary file
|
|
859
|
+
try {
|
|
860
|
+
unlinkSync(tempFile);
|
|
861
|
+
} catch (cleanupError) {
|
|
862
|
+
this.logger.log(`⚠️ [${ideName}] Failed to cleanup diagnostic temp file: ${cleanupError.message}`);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
} catch (diagnosticError) {
|
|
866
|
+
this.logger.log(`⚠️ [${ideName}] Diagnostic failed: ${diagnosticError.message}`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
try {
|
|
871
|
+
let appleScript;
|
|
872
|
+
|
|
873
|
+
if (ide === 'windsurf') {
|
|
874
|
+
// AppleScript for Windsurf - send text to existing process only
|
|
875
|
+
this.logger.log('🔄 [Windsurf] Sending text to existing Windsurf process...');
|
|
876
|
+
appleScript = `
|
|
877
|
+
tell application "System Events"
|
|
878
|
+
tell process "Windsurf"
|
|
879
|
+
-- Check if the process is running before attempting to interact
|
|
880
|
+
try
|
|
881
|
+
set frontmost to true
|
|
882
|
+
delay 1
|
|
883
|
+
|
|
884
|
+
-- Step 1: Open Command Palette to start a new chat
|
|
885
|
+
key code 35 using {command down, shift down}
|
|
886
|
+
delay 0.8
|
|
887
|
+
|
|
888
|
+
-- Step 2: Type "New Chat" to start a fresh conversation
|
|
889
|
+
keystroke "New Chat"
|
|
890
|
+
delay 0.8
|
|
891
|
+
|
|
892
|
+
-- Step 3: Press Enter to execute the command
|
|
893
|
+
key code 36
|
|
894
|
+
delay 2.0
|
|
895
|
+
|
|
896
|
+
-- Step 4: Now find and click on the chat input field
|
|
897
|
+
try
|
|
898
|
+
set chatInput to text field 1 of group 1 of window 1
|
|
899
|
+
click chatInput
|
|
900
|
+
delay 0.5
|
|
901
|
+
on error
|
|
902
|
+
try
|
|
903
|
+
-- Method 2: Try to find text area for chat
|
|
904
|
+
set chatInput to text area 1 of group 1 of window 1
|
|
905
|
+
click chatInput
|
|
906
|
+
delay 0.5
|
|
907
|
+
on error
|
|
908
|
+
try
|
|
909
|
+
-- Method 3: Try to find any input field
|
|
910
|
+
set chatInput to text field 1 of window 1
|
|
911
|
+
click chatInput
|
|
912
|
+
delay 0.5
|
|
913
|
+
on error
|
|
914
|
+
-- Method 4: Just try typing (fallback)
|
|
915
|
+
delay 0.5
|
|
916
|
+
end try
|
|
917
|
+
end try
|
|
918
|
+
end try
|
|
919
|
+
|
|
920
|
+
-- Type the message
|
|
921
|
+
keystroke "${text.replace(/"/g, '\\"')}"
|
|
922
|
+
delay 0.5
|
|
923
|
+
|
|
924
|
+
-- Try multiple submission methods
|
|
925
|
+
key code 36
|
|
926
|
+
delay 0.5
|
|
927
|
+
key code 36 using {command down}
|
|
928
|
+
delay 0.5
|
|
929
|
+
key code 36 using {command down, shift down}
|
|
930
|
+
delay 0.5
|
|
931
|
+
key code 1 using {command down}
|
|
932
|
+
delay 0.5
|
|
933
|
+
on error
|
|
934
|
+
-- Process not running or not accessible
|
|
935
|
+
error "Process 'Windsurf' is not running or not accessible. VibeCodingMachine does not open IDEs - please open Windsurf manually first."
|
|
936
|
+
end try
|
|
937
|
+
end tell
|
|
938
|
+
end tell
|
|
939
|
+
`;
|
|
940
|
+
} else if (ide === 'claude') {
|
|
941
|
+
// Use a different approach for Claude - find existing Claude terminal and send text
|
|
942
|
+
const targetRepoPath = repoPath || '/Users/jesse/code/mediawink/allnightai';
|
|
943
|
+
this.logger.log(`🔍 [Claude] Using repo path for terminal detection: "${targetRepoPath}" (passed: "${repoPath}")`);
|
|
944
|
+
|
|
945
|
+
// Check if Claude process is running
|
|
946
|
+
let claudeProcessCount = 0;
|
|
947
|
+
try {
|
|
948
|
+
const processCheck = execSync('ps aux | grep -i "claude" | grep -v grep | grep -v "Claude.app" | wc -l', { encoding: 'utf8' }).trim();
|
|
949
|
+
claudeProcessCount = parseInt(processCheck);
|
|
950
|
+
this.logger.log(`🔍 [Claude] Found ${claudeProcessCount} Claude processes`);
|
|
951
|
+
} catch (error) {
|
|
952
|
+
this.logger.log(`⚠️ [Claude] Process check failed: ${error.message}`);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
if (claudeProcessCount === 0) {
|
|
956
|
+
// No Claude process running - try to open it
|
|
957
|
+
this.logger.log('⚠️ [Claude] No Claude process found, attempting to open Claude...');
|
|
958
|
+
const openResult = await this.openClaude(repoPath);
|
|
959
|
+
|
|
960
|
+
if (!openResult.success) {
|
|
961
|
+
return {
|
|
962
|
+
success: false,
|
|
963
|
+
method: 'applescript',
|
|
964
|
+
error: `No Claude process found and failed to open Claude: ${openResult.error}`,
|
|
965
|
+
note: 'Attempted to open Claude but it failed'
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// Wait for Claude to fully start
|
|
970
|
+
this.logger.log('✅ [Claude] Claude opened successfully, waiting for it to be ready...');
|
|
971
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
972
|
+
|
|
973
|
+
// Re-check if Claude process is now running
|
|
974
|
+
try {
|
|
975
|
+
const recheckProcess = execSync('ps aux | grep -i "claude" | grep -v grep | grep -v "Claude.app" | wc -l', { encoding: 'utf8' }).trim();
|
|
976
|
+
const recheckCount = parseInt(recheckProcess);
|
|
977
|
+
if (recheckCount === 0) {
|
|
978
|
+
return {
|
|
979
|
+
success: false,
|
|
980
|
+
method: 'applescript',
|
|
981
|
+
error: 'Claude was opened but process not detected after waiting',
|
|
982
|
+
note: 'Claude may need more time to start'
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
this.logger.log(`✅ [Claude] Confirmed ${recheckCount} Claude processes running after opening`);
|
|
986
|
+
} catch (recheckError) {
|
|
987
|
+
this.logger.log(`⚠️ [Claude] Could not verify Claude process after opening: ${recheckError.message}`);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// Claude process exists - find and send text to the specific Claude terminal window
|
|
992
|
+
this.logger.log('🔄 [Claude] Finding specific Claude terminal window...');
|
|
993
|
+
|
|
994
|
+
// CRITICAL: Validate text parameter before using in template string
|
|
995
|
+
if (!text || typeof text !== 'string') {
|
|
996
|
+
this.logger.error(`❌ [Claude] Invalid text parameter: type=${typeof text}, value=${text}`);
|
|
997
|
+
return {
|
|
998
|
+
success: false,
|
|
999
|
+
method: 'applescript',
|
|
1000
|
+
error: `Invalid text parameter for Claude: received ${typeof text} instead of string`,
|
|
1001
|
+
debug: { textType: typeof text, textValue: text }
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
const escapedRepoPath = targetRepoPath.replace(/'/g, "'\\''");
|
|
1006
|
+
const findAndSendScript = `
|
|
1007
|
+
tell application "Terminal"
|
|
1008
|
+
activate
|
|
1009
|
+
|
|
1010
|
+
set claudeWindow to null
|
|
1011
|
+
set claudeTab to null
|
|
1012
|
+
set windowCount to count of windows
|
|
1013
|
+
|
|
1014
|
+
-- Search through all terminal windows to find the one running Claude IN THE CORRECT DIRECTORY
|
|
1015
|
+
repeat with i from 1 to windowCount
|
|
1016
|
+
try
|
|
1017
|
+
set currentWindow to window i
|
|
1018
|
+
set currentTab to selected tab of currentWindow
|
|
1019
|
+
set historyText to history of currentTab as text
|
|
1020
|
+
|
|
1021
|
+
-- Check for Claude AND the correct repo path to avoid sending to wrong terminal
|
|
1022
|
+
-- CRITICAL FIX: Must check BOTH Claude process AND repo path to avoid wrong terminal
|
|
1023
|
+
-- Also check that Claude is CURRENTLY RUNNING (not just in history)
|
|
1024
|
+
set hasClaudeCode to historyText contains "Claude Code" or historyText contains "claude --dangerously-skip-permissions"
|
|
1025
|
+
set hasRepoPath to historyText contains "${escapedRepoPath}"
|
|
1026
|
+
-- Check for Claude interactive prompts or working state
|
|
1027
|
+
set hasClaudePrompt to historyText contains "claude>" or historyText contains "What can I help you build" or historyText contains "bypass permissions"
|
|
1028
|
+
|
|
1029
|
+
if hasClaudeCode and hasRepoPath and hasClaudePrompt then
|
|
1030
|
+
set claudeWindow to currentWindow
|
|
1031
|
+
set claudeTab to currentTab
|
|
1032
|
+
exit repeat
|
|
1033
|
+
end if
|
|
1034
|
+
on error
|
|
1035
|
+
-- Continue to next window
|
|
1036
|
+
end try
|
|
1037
|
+
end repeat
|
|
1038
|
+
|
|
1039
|
+
if claudeWindow is not null then
|
|
1040
|
+
-- Found existing Claude terminal, bring it to front
|
|
1041
|
+
set index of claudeWindow to 1
|
|
1042
|
+
delay 1.0
|
|
1043
|
+
else
|
|
1044
|
+
error "No Claude terminal found in directory: ${escapedRepoPath}"
|
|
1045
|
+
end if
|
|
1046
|
+
end tell
|
|
1047
|
+
|
|
1048
|
+
-- CRITICAL: Must use System Events, NOT 'do script'
|
|
1049
|
+
-- REASON: 'do script' only submits when STARTING a command, not when process is already running
|
|
1050
|
+
-- This was the root cause of "Start Auto" not submitting text to Claude (Oct 17, 2025)
|
|
1051
|
+
-- DO NOT CHANGE THIS TO 'do script' - it will break text submission!
|
|
1052
|
+
tell application "System Events"
|
|
1053
|
+
tell process "Terminal"
|
|
1054
|
+
set frontmost to true
|
|
1055
|
+
delay 0.5
|
|
1056
|
+
|
|
1057
|
+
-- Type the text
|
|
1058
|
+
keystroke "${text.replace(/"/g, '\\"')}"
|
|
1059
|
+
delay 1.0
|
|
1060
|
+
|
|
1061
|
+
-- Submit with return (REQUIRED - 'do script' won't do this when Claude is running)
|
|
1062
|
+
keystroke return
|
|
1063
|
+
delay 0.3
|
|
1064
|
+
end tell
|
|
1065
|
+
end tell
|
|
1066
|
+
`;
|
|
1067
|
+
|
|
1068
|
+
// Execute the AppleScript using file-based approach
|
|
1069
|
+
try {
|
|
1070
|
+
const tempFile = join(tmpdir(), `claude_script_${Date.now()}.scpt`);
|
|
1071
|
+
writeFileSync(tempFile, findAndSendScript, 'utf8');
|
|
1072
|
+
|
|
1073
|
+
let result;
|
|
1074
|
+
try {
|
|
1075
|
+
result = execSync(`osascript "${tempFile}"`, {
|
|
1076
|
+
encoding: 'utf8',
|
|
1077
|
+
timeout: 30000, // 30 seconds to allow for window searching and delays
|
|
1078
|
+
stdio: 'pipe'
|
|
1079
|
+
}).toString().trim();
|
|
1080
|
+
} finally {
|
|
1081
|
+
// Clean up temp file
|
|
1082
|
+
try { unlinkSync(tempFile); } catch (cleanupError) { this.logger.log(`⚠️ [Claude] Failed to cleanup temp file: ${cleanupError.message}`); }
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
this.logger.log(`✅ [Claude] AppleScript execution output:`, result);
|
|
1086
|
+
|
|
1087
|
+
// Verify that the sent text appears in the returned history snippet
|
|
1088
|
+
try {
|
|
1089
|
+
const sentVerified = result && result.indexOf('|HIST:') !== -1 && result.split('|HIST:')[1].includes(`${text}`);
|
|
1090
|
+
if (sentVerified) {
|
|
1091
|
+
this.logger.log('✅ [Claude] Verified sent text present in terminal history snippet');
|
|
1092
|
+
return {
|
|
1093
|
+
success: true,
|
|
1094
|
+
method: 'applescript',
|
|
1095
|
+
message: `Text sent and verified in Claude terminal: ${text}`,
|
|
1096
|
+
note: result
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
this.logger.log('⚠️ [Claude] Sent text NOT found in terminal history snippet; will attempt deterministic retry');
|
|
1100
|
+
} catch (verifyErr) {
|
|
1101
|
+
this.logger.log('⚠️ [Claude] Verification parse error:', verifyErr.message || verifyErr);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// If verification failed, attempt a deterministic direct send to window 1 tab 1
|
|
1105
|
+
try {
|
|
1106
|
+
const det = await this.sendDeterministicToTerminalWindow1(text);
|
|
1107
|
+
if (det && det.success) {
|
|
1108
|
+
this.logger.log('✅ [Claude] Deterministic retry verified and succeeded');
|
|
1109
|
+
return { success: true, method: 'applescript', message: `Text sent to newly opened Claude terminal: ${text}`, note: det.note };
|
|
1110
|
+
}
|
|
1111
|
+
this.logger.log('⚠️ [Claude] Deterministic retry did not verify:', det && det.note ? det.note : det && det.error ? det.error : det);
|
|
1112
|
+
} catch (detErr) {
|
|
1113
|
+
this.logger.log('❌ [Claude] Deterministic retry failed:', detErr.message || detErr);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// CRITICAL FIX: If we reach here, text was sent but not verified
|
|
1117
|
+
// Return success anyway since the AppleScript execution succeeded
|
|
1118
|
+
this.logger.log('⚠️ [Claude] Text sent but verification inconclusive, returning success');
|
|
1119
|
+
return {
|
|
1120
|
+
success: true,
|
|
1121
|
+
method: 'applescript',
|
|
1122
|
+
message: `Text sent to Claude terminal (verification inconclusive): ${text}`,
|
|
1123
|
+
note: 'Text sent successfully but verification did not confirm'
|
|
1124
|
+
};
|
|
1125
|
+
} catch (error) {
|
|
1126
|
+
this.logger.log('❌ [Claude] AppleScript failed:', error.message);
|
|
1127
|
+
return {
|
|
1128
|
+
success: false,
|
|
1129
|
+
method: 'applescript',
|
|
1130
|
+
error: `Failed to send text to Claude: ${error.message}`,
|
|
1131
|
+
note: 'Claude AppleScript automation failed'
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
} else if (ide === 'cursor') {
|
|
1135
|
+
// Cursor already has new chat logic in generateAIPanelFocusScript (Cmd+T)
|
|
1136
|
+
appleScript = this.appleScriptUtils.generateAIPanelFocusScript(text, 'Cursor');
|
|
1137
|
+
} else if (ide === 'vscode') {
|
|
1138
|
+
// Delegate to the dedicated VS Code method
|
|
1139
|
+
return await this.sendTextToVSCode(text);
|
|
1140
|
+
} else if (ide === 'antigravity') {
|
|
1141
|
+
// AppleScript for Google Antigravity - Cmd+Shift+L opens Agent chat (non-toggle)
|
|
1142
|
+
this.logger.log('🔄 [Antigravity] Sending text to Antigravity Agent chat...');
|
|
1143
|
+
appleScript = `
|
|
1144
|
+
tell application "System Events"
|
|
1145
|
+
tell process "Antigravity"
|
|
1146
|
+
-- Check if the process is running before attempting to interact
|
|
1147
|
+
try
|
|
1148
|
+
set frontmost to true
|
|
1149
|
+
delay 1
|
|
1150
|
+
|
|
1151
|
+
-- Open Agent chat with Cmd+Shift+L (does not toggle)
|
|
1152
|
+
key code 37 using {command down, shift down}
|
|
1153
|
+
delay 1.5
|
|
1154
|
+
|
|
1155
|
+
-- Type the message
|
|
1156
|
+
keystroke "${text.replace(/"/g, '\\"')}"
|
|
1157
|
+
delay 0.5
|
|
1158
|
+
|
|
1159
|
+
-- Send with Enter
|
|
1160
|
+
key code 36
|
|
1161
|
+
delay 0.5
|
|
1162
|
+
|
|
1163
|
+
return "Message sent to Antigravity Agent"
|
|
1164
|
+
on error
|
|
1165
|
+
-- Process not running or not accessible
|
|
1166
|
+
error "Process 'Antigravity' is not running or not accessible. VibeCodingMachine does not open IDEs - please open Antigravity manually first."
|
|
1167
|
+
end try
|
|
1168
|
+
end tell
|
|
1169
|
+
end tell
|
|
1170
|
+
`;
|
|
1171
|
+
} else {
|
|
1172
|
+
return {
|
|
1173
|
+
success: false,
|
|
1174
|
+
error: `Unsupported IDE for AppleScript: ${ide}`,
|
|
1175
|
+
note: 'AppleScript is only supported for Cursor, Windsurf, Antigravity, and VS Code'
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
this.logger.log(`🚀 [${ideName}] Executing AppleScript with PRECISE CHAT TARGETING...`);
|
|
1180
|
+
|
|
1181
|
+
// Guard against undefined appleScript (can happen with Claude path)
|
|
1182
|
+
if (!appleScript) {
|
|
1183
|
+
this.logger.error(`❌ [${ideName}] AppleScript variable is undefined - invalid code path`);
|
|
1184
|
+
return {
|
|
1185
|
+
success: false,
|
|
1186
|
+
method: 'applescript',
|
|
1187
|
+
error: 'AppleScript not generated - this should not happen for non-Claude IDEs',
|
|
1188
|
+
note: 'Check IDE-specific code path logic'
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// FIXED: Use file-based AppleScript execution to avoid shell escaping issues
|
|
1193
|
+
const tempFile = join(tmpdir(), `applescript_${Date.now()}.scpt`);
|
|
1194
|
+
try {
|
|
1195
|
+
writeFileSync(tempFile, appleScript, 'utf8');
|
|
1196
|
+
execSync(`osascript "${tempFile}"`, { stdio: 'pipe' });
|
|
1197
|
+
this.logger.log(`✅ [${ideName}] AppleScript executed successfully - PRECISE TARGETING`);
|
|
1198
|
+
this.logger.log(`✅ [${ideName}] Message sent successfully via precise element targeting`);
|
|
1199
|
+
return {
|
|
1200
|
+
success: true,
|
|
1201
|
+
method: 'applescript',
|
|
1202
|
+
message: `Message sent to ${ideName}: ${text}`,
|
|
1203
|
+
note: 'Message sent via AppleScript automation'
|
|
1204
|
+
};
|
|
1205
|
+
} finally {
|
|
1206
|
+
// Clean up temporary file
|
|
1207
|
+
try {
|
|
1208
|
+
unlinkSync(tempFile);
|
|
1209
|
+
} catch (cleanupError) {
|
|
1210
|
+
this.logger.log(`⚠️ [${ideName}] Failed to cleanup temp file: ${cleanupError.message}`);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
} catch (error) {
|
|
1215
|
+
this.logger.log('AppleScript interaction failed:', error.message);
|
|
1216
|
+
|
|
1217
|
+
// For Claude, don't fall back to simulated response - return actual failure
|
|
1218
|
+
if (ide === 'claude') {
|
|
1219
|
+
return {
|
|
1220
|
+
success: false,
|
|
1221
|
+
method: 'applescript',
|
|
1222
|
+
error: `Failed to send text to Claude: ${error.message}`,
|
|
1223
|
+
note: 'Claude AppleScript automation failed. Check if Claude is running and accessible.'
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// For other IDEs, fall back to simulated response
|
|
1228
|
+
return {
|
|
1229
|
+
success: true,
|
|
1230
|
+
method: 'simulated',
|
|
1231
|
+
message: `Simulated ${ideName} response: ${text}`,
|
|
1232
|
+
note: `${ideName} AppleScript automation failed. Using simulated response for testing.`
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
/**
|
|
1238
|
+
* Check Claude terminal output for session limit errors
|
|
1239
|
+
* Reads the terminal history and detects if Claude has hit its usage limit
|
|
1240
|
+
* @returns {Promise<Object>} Result object with sessionLimitReached flag and details
|
|
1241
|
+
*/
|
|
1242
|
+
async checkClaudeSessionLimit() {
|
|
1243
|
+
try {
|
|
1244
|
+
this.logger.log('🔍 Checking Claude terminal for session limit errors...');
|
|
1245
|
+
|
|
1246
|
+
const checkScript = `
|
|
1247
|
+
tell application "Terminal"
|
|
1248
|
+
set claudeWindow to null
|
|
1249
|
+
set claudeTab to null
|
|
1250
|
+
set windowCount to count of windows
|
|
1251
|
+
|
|
1252
|
+
-- Search through all terminal windows to find the one running Claude
|
|
1253
|
+
repeat with i from 1 to windowCount
|
|
1254
|
+
try
|
|
1255
|
+
set currentWindow to window i
|
|
1256
|
+
set currentTab to selected tab of currentWindow
|
|
1257
|
+
set historyText to history of currentTab as text
|
|
1258
|
+
|
|
1259
|
+
-- Check if this is the Claude terminal
|
|
1260
|
+
if historyText contains "Claude Code" or historyText contains "claude --dangerously-skip-permissions" then
|
|
1261
|
+
set claudeWindow to currentWindow
|
|
1262
|
+
set claudeTab to currentTab
|
|
1263
|
+
exit repeat
|
|
1264
|
+
end if
|
|
1265
|
+
on error
|
|
1266
|
+
-- Continue to next window
|
|
1267
|
+
end try
|
|
1268
|
+
end repeat
|
|
1269
|
+
|
|
1270
|
+
if claudeWindow is not null then
|
|
1271
|
+
-- Get the last ~500 characters of history to check for recent errors
|
|
1272
|
+
set fullHistory to history of claudeTab as text
|
|
1273
|
+
set historyLength to length of fullHistory
|
|
1274
|
+
set recentHistory to ""
|
|
1275
|
+
|
|
1276
|
+
if historyLength > 500 then
|
|
1277
|
+
set recentHistory to text -500 thru -1 of fullHistory
|
|
1278
|
+
else
|
|
1279
|
+
set recentHistory to fullHistory
|
|
1280
|
+
end if
|
|
1281
|
+
|
|
1282
|
+
return recentHistory
|
|
1283
|
+
else
|
|
1284
|
+
error "No Claude terminal found"
|
|
1285
|
+
end if
|
|
1286
|
+
end tell
|
|
1287
|
+
`;
|
|
1288
|
+
|
|
1289
|
+
const tempFile = join(tmpdir(), `check_claude_limit_${Date.now()}.scpt`);
|
|
1290
|
+
writeFileSync(tempFile, checkScript, 'utf8');
|
|
1291
|
+
|
|
1292
|
+
const recentOutput = execSync(`osascript "${tempFile}"`, {
|
|
1293
|
+
encoding: 'utf8',
|
|
1294
|
+
timeout: 10000,
|
|
1295
|
+
stdio: 'pipe'
|
|
1296
|
+
}).trim();
|
|
1297
|
+
|
|
1298
|
+
// Clean up temp file
|
|
1299
|
+
try {
|
|
1300
|
+
unlinkSync(tempFile);
|
|
1301
|
+
} catch (cleanupError) {
|
|
1302
|
+
this.logger.log(`⚠️ Failed to cleanup temp file: ${cleanupError.message}`);
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
// Check for session limit patterns
|
|
1306
|
+
const sessionLimitPatterns = [
|
|
1307
|
+
'Session limit reached',
|
|
1308
|
+
'session limit reached',
|
|
1309
|
+
'usage limit',
|
|
1310
|
+
'/upgrade to increase your usage limit',
|
|
1311
|
+
'resets at',
|
|
1312
|
+
/resets \d{1,2}(am|pm)/i // Matches "resets 7pm", "resets 10am", etc.
|
|
1313
|
+
];
|
|
1314
|
+
|
|
1315
|
+
const sessionLimitReached = sessionLimitPatterns.some(pattern => {
|
|
1316
|
+
if (pattern instanceof RegExp) {
|
|
1317
|
+
return pattern.test(recentOutput);
|
|
1318
|
+
}
|
|
1319
|
+
return recentOutput.toLowerCase().includes(pattern.toLowerCase());
|
|
1320
|
+
});
|
|
1321
|
+
|
|
1322
|
+
if (sessionLimitReached) {
|
|
1323
|
+
this.logger.log('🚨 SESSION LIMIT DETECTED in Claude terminal output');
|
|
1324
|
+
this.logger.log('📋 Recent output:', recentOutput.substring(Math.max(0, recentOutput.length - 200)));
|
|
1325
|
+
|
|
1326
|
+
// Extract reset time if present (e.g., "resets 10am", "resets 7pm")
|
|
1327
|
+
const resetTimeMatch = recentOutput.match(/resets\s+(\d{1,2})(am|pm)/i);
|
|
1328
|
+
let resetTime = null;
|
|
1329
|
+
if (resetTimeMatch) {
|
|
1330
|
+
resetTime = `${resetTimeMatch[1]}${resetTimeMatch[2].toLowerCase()}`;
|
|
1331
|
+
this.logger.log(`🕐 Reset time detected: ${resetTime}`);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
return {
|
|
1335
|
+
sessionLimitReached: true,
|
|
1336
|
+
message: 'Claude session limit has been reached',
|
|
1337
|
+
resetTime: resetTime, // Add reset time to response
|
|
1338
|
+
recentOutput: recentOutput,
|
|
1339
|
+
note: resetTime
|
|
1340
|
+
? `Claude usage limit exceeded - resets at ${resetTime}. Please wait or upgrade.`
|
|
1341
|
+
: 'Claude usage limit exceeded - please wait for reset or upgrade'
|
|
1342
|
+
};
|
|
1343
|
+
} else {
|
|
1344
|
+
this.logger.log('✅ No session limit detected');
|
|
1345
|
+
return {
|
|
1346
|
+
sessionLimitReached: false,
|
|
1347
|
+
message: 'No session limit detected',
|
|
1348
|
+
recentOutput: recentOutput
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
} catch (error) {
|
|
1353
|
+
this.logger.log('❌ Error checking Claude session limit:', error.message);
|
|
1354
|
+
return {
|
|
1355
|
+
sessionLimitReached: false,
|
|
1356
|
+
error: error.message,
|
|
1357
|
+
message: 'Failed to check session limit'
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
/**
|
|
1363
|
+
* Send text to IDE on Windows platform using PowerShell automation
|
|
1364
|
+
* @param {string} text - The text to send
|
|
1365
|
+
* @param {string} ide - The IDE name
|
|
1366
|
+
* @returns {Promise<Object>} Result object with success status and details
|
|
1367
|
+
*/
|
|
1368
|
+
async sendTextWindows(text, ide) {
|
|
1369
|
+
if (!this.windowsManager) {
|
|
1370
|
+
return {
|
|
1371
|
+
success: false,
|
|
1372
|
+
error: 'Windows automation manager not available',
|
|
1373
|
+
method: 'windows-automation'
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
try {
|
|
1378
|
+
this.logger.log(`🔧 Windows: Sending text to ${ide}...`);
|
|
1379
|
+
|
|
1380
|
+
switch (ide.toLowerCase()) {
|
|
1381
|
+
case 'cursor':
|
|
1382
|
+
// Try the native Windows API approach first, then fallback to basic PowerShell
|
|
1383
|
+
let result = await this.windowsManager.sendTextToCursorNative(text);
|
|
1384
|
+
if (!result.success) {
|
|
1385
|
+
this.logger.log('🔧 Windows: Native approach failed, trying basic PowerShell...');
|
|
1386
|
+
result = await this.windowsManager.sendTextToCursor(text);
|
|
1387
|
+
}
|
|
1388
|
+
return result;
|
|
1389
|
+
|
|
1390
|
+
case 'vscode':
|
|
1391
|
+
case 'windsurf':
|
|
1392
|
+
// For now, use the same approach as Cursor for other IDEs
|
|
1393
|
+
return await this.windowsManager.sendTextToCursor(text);
|
|
1394
|
+
|
|
1395
|
+
default:
|
|
1396
|
+
return {
|
|
1397
|
+
success: false,
|
|
1398
|
+
error: `Windows automation not implemented for IDE: ${ide}`,
|
|
1399
|
+
method: 'windows-automation'
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
} catch (error) {
|
|
1403
|
+
this.logger.error(`❌ Windows: Error sending text to ${ide}:`, error.message);
|
|
1404
|
+
return {
|
|
1405
|
+
success: false,
|
|
1406
|
+
error: `Windows automation error: ${error.message}`,
|
|
1407
|
+
method: 'windows-automation'
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
/**
|
|
1413
|
+
* Read chat response from IDE using AppleScript
|
|
1414
|
+
* @param {string} ide - The IDE name ('cursor' or 'windsurf')
|
|
1415
|
+
* @returns {Promise<string>} The chat response text
|
|
1416
|
+
*/
|
|
1417
|
+
async readChatResponse(ide) {
|
|
1418
|
+
if (ide !== 'windsurf' && ide !== 'cursor' && ide !== 'vscode') {
|
|
1419
|
+
return 'Error: AppleScript reading is only supported for Cursor, Windsurf, and VS Code';
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
const ideName = ide === 'windsurf' ? 'Windsurf' : ide === 'cursor' ? 'Cursor' : ide === 'vscode' ? 'VS Code' : 'Unknown';
|
|
1423
|
+
this.logger.log(`${ideName} detected - using AppleScript to read chat response`);
|
|
1424
|
+
|
|
1425
|
+
try {
|
|
1426
|
+
// For Cursor, try CDP first if available
|
|
1427
|
+
if (ide === 'cursor') {
|
|
1428
|
+
try {
|
|
1429
|
+
this.logger.log('🔧 Attempting CDP method for Cursor...');
|
|
1430
|
+
const cdpResponse = await this.readCursorResponseViaCDP();
|
|
1431
|
+
if (cdpResponse && cdpResponse.length > 20) {
|
|
1432
|
+
this.logger.log('✅ Successfully read Cursor response via CDP');
|
|
1433
|
+
return cdpResponse;
|
|
1434
|
+
}
|
|
1435
|
+
} catch (error) {
|
|
1436
|
+
this.logger.log('⚠️ CDP method failed, falling back to AppleScript:', error.message);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// Try clipboard-based response detection
|
|
1441
|
+
if (ide === 'cursor') {
|
|
1442
|
+
try {
|
|
1443
|
+
this.logger.log('🔧 Attempting clipboard-based response detection...');
|
|
1444
|
+
const clipboardResponse = await this.readCursorResponseViaClipboard();
|
|
1445
|
+
if (clipboardResponse && clipboardResponse.length > 20) {
|
|
1446
|
+
this.logger.log('✅ Successfully read Cursor response via clipboard');
|
|
1447
|
+
return clipboardResponse;
|
|
1448
|
+
}
|
|
1449
|
+
} catch (error) {
|
|
1450
|
+
this.logger.log('⚠️ Clipboard method failed, falling back to AppleScript:', error.message);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
let appleScript;
|
|
1455
|
+
|
|
1456
|
+
if (ide === 'cursor') {
|
|
1457
|
+
// Enhanced AppleScript to read the chat response from Cursor
|
|
1458
|
+
appleScript = `
|
|
1459
|
+
tell application "System Events"
|
|
1460
|
+
tell process "Cursor"
|
|
1461
|
+
set frontmost to true
|
|
1462
|
+
delay 1
|
|
1463
|
+
|
|
1464
|
+
-- Method 1: Try to get text from web view content (most likely location)
|
|
1465
|
+
try
|
|
1466
|
+
set webViewGroups to group of window 1
|
|
1467
|
+
set responseText to ""
|
|
1468
|
+
repeat with grp in webViewGroups
|
|
1469
|
+
try
|
|
1470
|
+
-- Look for text areas within groups (web view content)
|
|
1471
|
+
set textAreas to text area of grp
|
|
1472
|
+
repeat with txtArea in textAreas
|
|
1473
|
+
try
|
|
1474
|
+
set textValue to value of txtArea
|
|
1475
|
+
if textValue is not "" and textValue is not missing value then
|
|
1476
|
+
set responseText to responseText & textValue & "\\n"
|
|
1477
|
+
end if
|
|
1478
|
+
on error
|
|
1479
|
+
-- Continue to next text area
|
|
1480
|
+
end try
|
|
1481
|
+
end repeat
|
|
1482
|
+
|
|
1483
|
+
-- Look for static text within groups
|
|
1484
|
+
set staticTexts to static text of grp
|
|
1485
|
+
repeat with txt in staticTexts
|
|
1486
|
+
try
|
|
1487
|
+
set textValue to value of txt
|
|
1488
|
+
if textValue is not "" and textValue is not missing value then
|
|
1489
|
+
set responseText to responseText & textValue & "\\n"
|
|
1490
|
+
end if
|
|
1491
|
+
on error
|
|
1492
|
+
-- Continue to next static text
|
|
1493
|
+
end try
|
|
1494
|
+
end repeat
|
|
1495
|
+
on error
|
|
1496
|
+
-- Continue to next group
|
|
1497
|
+
end try
|
|
1498
|
+
end repeat
|
|
1499
|
+
if responseText is not "" then
|
|
1500
|
+
return responseText
|
|
1501
|
+
end if
|
|
1502
|
+
on error
|
|
1503
|
+
-- Continue to next method
|
|
1504
|
+
end try
|
|
1505
|
+
|
|
1506
|
+
-- Method 2: Try to get text from all text areas in the window
|
|
1507
|
+
try
|
|
1508
|
+
set allTextAreas to text area of window 1
|
|
1509
|
+
set responseText to ""
|
|
1510
|
+
repeat with txtArea in allTextAreas
|
|
1511
|
+
try
|
|
1512
|
+
set textValue to value of txtArea
|
|
1513
|
+
if textValue is not "" and textValue is not missing value then
|
|
1514
|
+
set responseText to responseText & textValue & "\\n"
|
|
1515
|
+
end if
|
|
1516
|
+
on error
|
|
1517
|
+
-- Continue to next text area
|
|
1518
|
+
end try
|
|
1519
|
+
end repeat
|
|
1520
|
+
if responseText is not "" then
|
|
1521
|
+
return responseText
|
|
1522
|
+
end if
|
|
1523
|
+
on error
|
|
1524
|
+
-- Continue to next method
|
|
1525
|
+
end try
|
|
1526
|
+
|
|
1527
|
+
-- Method 3: Try to get text from all static text elements
|
|
1528
|
+
try
|
|
1529
|
+
set allTexts to static text of window 1
|
|
1530
|
+
set responseText to ""
|
|
1531
|
+
repeat with txt in allTexts
|
|
1532
|
+
try
|
|
1533
|
+
set textValue to value of txt
|
|
1534
|
+
if textValue is not "" and textValue is not missing value then
|
|
1535
|
+
set responseText to responseText & textValue & "\\n"
|
|
1536
|
+
end if
|
|
1537
|
+
on error
|
|
1538
|
+
-- Continue to next text element
|
|
1539
|
+
end try
|
|
1540
|
+
end repeat
|
|
1541
|
+
if responseText is not "" then
|
|
1542
|
+
return responseText
|
|
1543
|
+
end if
|
|
1544
|
+
on error
|
|
1545
|
+
-- Continue to next method
|
|
1546
|
+
end try
|
|
1547
|
+
|
|
1548
|
+
-- Method 4: Try clipboard approach (select all and copy)
|
|
1549
|
+
try
|
|
1550
|
+
key code 0 using {command down}
|
|
1551
|
+
delay 0.5
|
|
1552
|
+
key code 8 using {command down}
|
|
1553
|
+
delay 0.5
|
|
1554
|
+
set clipboardText to (the clipboard)
|
|
1555
|
+
if clipboardText is not "" then
|
|
1556
|
+
return clipboardText
|
|
1557
|
+
end if
|
|
1558
|
+
on error
|
|
1559
|
+
-- Continue to next method
|
|
1560
|
+
end try
|
|
1561
|
+
|
|
1562
|
+
-- Method 5: Try to find scroll areas and get their content
|
|
1563
|
+
try
|
|
1564
|
+
set scrollAreas to scroll area of window 1
|
|
1565
|
+
set responseText to ""
|
|
1566
|
+
repeat with scrollArea in scrollAreas
|
|
1567
|
+
try
|
|
1568
|
+
set scrollTexts to static text of scrollArea
|
|
1569
|
+
repeat with txt in scrollTexts
|
|
1570
|
+
try
|
|
1571
|
+
set textValue to value of txt
|
|
1572
|
+
if textValue is not "" and textValue is not missing value then
|
|
1573
|
+
set responseText to responseText & textValue & "\\n"
|
|
1574
|
+
end if
|
|
1575
|
+
on error
|
|
1576
|
+
-- Continue to next text
|
|
1577
|
+
end try
|
|
1578
|
+
end repeat
|
|
1579
|
+
on error
|
|
1580
|
+
-- Continue to next scroll area
|
|
1581
|
+
end try
|
|
1582
|
+
end repeat
|
|
1583
|
+
if responseText is not "" then
|
|
1584
|
+
return responseText
|
|
1585
|
+
end if
|
|
1586
|
+
on error
|
|
1587
|
+
-- Continue to next method
|
|
1588
|
+
end try
|
|
1589
|
+
|
|
1590
|
+
-- Method 6: Try to access web view content through accessibility
|
|
1591
|
+
try
|
|
1592
|
+
set webViews to group of window 1
|
|
1593
|
+
set responseText to ""
|
|
1594
|
+
repeat with webView in webViews
|
|
1595
|
+
try
|
|
1596
|
+
-- Try to get all accessible elements within the web view
|
|
1597
|
+
set accessibleElements to every UI element of webView
|
|
1598
|
+
repeat with element in accessibleElements
|
|
1599
|
+
try
|
|
1600
|
+
set elementValue to value of element
|
|
1601
|
+
if elementValue is not "" and elementValue is not missing value then
|
|
1602
|
+
set responseText to responseText & elementValue & "\\n"
|
|
1603
|
+
end if
|
|
1604
|
+
on error
|
|
1605
|
+
-- Continue to next element
|
|
1606
|
+
end try
|
|
1607
|
+
end repeat
|
|
1608
|
+
on error
|
|
1609
|
+
-- Continue to next web view
|
|
1610
|
+
end try
|
|
1611
|
+
end repeat
|
|
1612
|
+
if responseText is not "" then
|
|
1613
|
+
return responseText
|
|
1614
|
+
end if
|
|
1615
|
+
on error
|
|
1616
|
+
-- Continue to next method
|
|
1617
|
+
end try
|
|
1618
|
+
|
|
1619
|
+
-- Method 7: Try to use JavaScript injection (if possible)
|
|
1620
|
+
try
|
|
1621
|
+
-- This is a fallback method that might work in some cases
|
|
1622
|
+
set responseText to ""
|
|
1623
|
+
set allElements to every UI element of window 1
|
|
1624
|
+
repeat with element in allElements
|
|
1625
|
+
try
|
|
1626
|
+
set elementValue to value of element
|
|
1627
|
+
if elementValue is not "" and elementValue is not missing value then
|
|
1628
|
+
set responseText to responseText & elementValue & "\\n"
|
|
1629
|
+
end if
|
|
1630
|
+
on error
|
|
1631
|
+
-- Continue to next element
|
|
1632
|
+
end try
|
|
1633
|
+
end repeat
|
|
1634
|
+
if responseText is not "" then
|
|
1635
|
+
return responseText
|
|
1636
|
+
end if
|
|
1637
|
+
on error
|
|
1638
|
+
-- Continue to next method
|
|
1639
|
+
end try
|
|
1640
|
+
|
|
1641
|
+
-- Method 8: Return diagnostic information
|
|
1642
|
+
return "No chat content found in Cursor - diagnostic info: Window exists but no readable text content detected. This may be because Cursor's chat interface is in a web view that AppleScript cannot access directly."
|
|
1643
|
+
end tell
|
|
1644
|
+
end tell
|
|
1645
|
+
`;
|
|
1646
|
+
} else if (ide === 'windsurf') {
|
|
1647
|
+
// AppleScript to read the chat response from Windsurf
|
|
1648
|
+
appleScript = `
|
|
1649
|
+
tell application "System Events"
|
|
1650
|
+
tell process "Windsurf"
|
|
1651
|
+
set frontmost to true
|
|
1652
|
+
delay 1
|
|
1653
|
+
|
|
1654
|
+
-- Method 1: Try to get all static text from the window
|
|
1655
|
+
try
|
|
1656
|
+
set allText to value of static text of window 1
|
|
1657
|
+
if allText is not "" then
|
|
1658
|
+
return allText
|
|
1659
|
+
end if
|
|
1660
|
+
on error
|
|
1661
|
+
-- Continue to next method
|
|
1662
|
+
end try
|
|
1663
|
+
|
|
1664
|
+
-- Method 2: Try to get text from groups
|
|
1665
|
+
try
|
|
1666
|
+
set groups to group of window 1
|
|
1667
|
+
set responseText to ""
|
|
1668
|
+
repeat with grp in groups
|
|
1669
|
+
try
|
|
1670
|
+
set groupText to value of static text of grp
|
|
1671
|
+
if groupText is not "" then
|
|
1672
|
+
set responseText to responseText & groupText & "\\n"
|
|
1673
|
+
end if
|
|
1674
|
+
on error
|
|
1675
|
+
-- Continue to next group
|
|
1676
|
+
end try
|
|
1677
|
+
end repeat
|
|
1678
|
+
if responseText is not "" then
|
|
1679
|
+
return responseText
|
|
1680
|
+
end if
|
|
1681
|
+
on error
|
|
1682
|
+
-- Continue to next method
|
|
1683
|
+
end try
|
|
1684
|
+
|
|
1685
|
+
-- Method 3: Try clipboard approach
|
|
1686
|
+
try
|
|
1687
|
+
key code 0 using {command down}
|
|
1688
|
+
delay 0.5
|
|
1689
|
+
key code 8 using {command down}
|
|
1690
|
+
delay 0.5
|
|
1691
|
+
set clipboardText to (the clipboard)
|
|
1692
|
+
if clipboardText is not "" then
|
|
1693
|
+
return clipboardText
|
|
1694
|
+
end if
|
|
1695
|
+
on error
|
|
1696
|
+
-- Continue to next method
|
|
1697
|
+
end try
|
|
1698
|
+
|
|
1699
|
+
-- Method 4: Return placeholder
|
|
1700
|
+
return "No chat content found in Windsurf"
|
|
1701
|
+
end tell
|
|
1702
|
+
end tell
|
|
1703
|
+
`;
|
|
1704
|
+
} else if (ide === 'vscode') {
|
|
1705
|
+
// AppleScript to read the chat response from VS Code
|
|
1706
|
+
appleScript = `
|
|
1707
|
+
tell application "System Events"
|
|
1708
|
+
tell process "Visual Studio Code"
|
|
1709
|
+
set frontmost to true
|
|
1710
|
+
delay 1
|
|
1711
|
+
|
|
1712
|
+
-- Method 1: Try to get all static text from the window (GitHub Copilot Chat)
|
|
1713
|
+
try
|
|
1714
|
+
set allText to value of static text of window 1
|
|
1715
|
+
if allText is not "" then
|
|
1716
|
+
return allText
|
|
1717
|
+
end if
|
|
1718
|
+
on error
|
|
1719
|
+
-- Continue to next method
|
|
1720
|
+
end try
|
|
1721
|
+
|
|
1722
|
+
-- Method 2: Try to get text from groups (chat panels)
|
|
1723
|
+
try
|
|
1724
|
+
set groups to group of window 1
|
|
1725
|
+
set responseText to ""
|
|
1726
|
+
repeat with grp in groups
|
|
1727
|
+
try
|
|
1728
|
+
set groupText to value of static text of grp
|
|
1729
|
+
if groupText is not "" then
|
|
1730
|
+
set responseText to responseText & groupText & "\\n"
|
|
1731
|
+
end if
|
|
1732
|
+
on error
|
|
1733
|
+
-- Continue to next group
|
|
1734
|
+
end try
|
|
1735
|
+
end repeat
|
|
1736
|
+
if responseText is not "" then
|
|
1737
|
+
return responseText
|
|
1738
|
+
end if
|
|
1739
|
+
on error
|
|
1740
|
+
-- Continue to next method
|
|
1741
|
+
end try
|
|
1742
|
+
|
|
1743
|
+
-- Method 3: Try to get text from text areas (chat input/output areas)
|
|
1744
|
+
try
|
|
1745
|
+
set textAreas to text area of window 1
|
|
1746
|
+
set responseText to ""
|
|
1747
|
+
repeat with txtArea in textAreas
|
|
1748
|
+
try
|
|
1749
|
+
set textValue to value of txtArea
|
|
1750
|
+
if textValue is not "" and textValue is not missing value then
|
|
1751
|
+
set responseText to responseText & textValue & "\\n"
|
|
1752
|
+
end if
|
|
1753
|
+
on error
|
|
1754
|
+
-- Continue to next text area
|
|
1755
|
+
end try
|
|
1756
|
+
end repeat
|
|
1757
|
+
if responseText is not "" then
|
|
1758
|
+
return responseText
|
|
1759
|
+
end if
|
|
1760
|
+
on error
|
|
1761
|
+
-- Continue to next method
|
|
1762
|
+
end try
|
|
1763
|
+
|
|
1764
|
+
-- Method 4: Try clipboard approach (select all chat content and copy)
|
|
1765
|
+
try
|
|
1766
|
+
key code 0 using {command down}
|
|
1767
|
+
delay 0.5
|
|
1768
|
+
key code 8 using {command down}
|
|
1769
|
+
delay 0.5
|
|
1770
|
+
set clipboardText to (the clipboard)
|
|
1771
|
+
if clipboardText is not "" then
|
|
1772
|
+
return clipboardText
|
|
1773
|
+
end if
|
|
1774
|
+
on error
|
|
1775
|
+
-- Continue to next method
|
|
1776
|
+
end try
|
|
1777
|
+
|
|
1778
|
+
-- Method 5: Try to access scroll areas (chat history)
|
|
1779
|
+
try
|
|
1780
|
+
set scrollAreas to scroll area of window 1
|
|
1781
|
+
set responseText to ""
|
|
1782
|
+
repeat with scrollArea in scrollAreas
|
|
1783
|
+
try
|
|
1784
|
+
set scrollTexts to static text of scrollArea
|
|
1785
|
+
repeat with txt in scrollTexts
|
|
1786
|
+
try
|
|
1787
|
+
set textValue to value of txt
|
|
1788
|
+
if textValue is not "" and textValue is not missing value then
|
|
1789
|
+
set responseText to responseText & textValue & "\\n"
|
|
1790
|
+
end if
|
|
1791
|
+
on error
|
|
1792
|
+
-- Continue to next text
|
|
1793
|
+
end try
|
|
1794
|
+
end repeat
|
|
1795
|
+
on error
|
|
1796
|
+
-- Continue to next scroll area
|
|
1797
|
+
end try
|
|
1798
|
+
end repeat
|
|
1799
|
+
if responseText is not "" then
|
|
1800
|
+
return responseText
|
|
1801
|
+
end if
|
|
1802
|
+
on error
|
|
1803
|
+
-- Continue to next method
|
|
1804
|
+
end try
|
|
1805
|
+
|
|
1806
|
+
-- Method 6: Return placeholder
|
|
1807
|
+
return "No chat content found in VS Code - ensure GitHub Copilot Chat or Continue extension is active"
|
|
1808
|
+
end tell
|
|
1809
|
+
end tell
|
|
1810
|
+
`;
|
|
1811
|
+
} else {
|
|
1812
|
+
return `Error: AppleScript reading is not supported for IDE: ${ide}`;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
const result = execSync(`osascript -e '${appleScript}'`, { stdio: 'pipe', encoding: 'utf8' });
|
|
1816
|
+
this.logger.log(`Successfully read ${ide} response via AppleScript`);
|
|
1817
|
+
this.logger.log('📖 AppleScript result:', result);
|
|
1818
|
+
this.logger.log('📏 Result length:', result.length);
|
|
1819
|
+
this.logger.log('🔍 Result preview:', result.substring(0, 200));
|
|
1820
|
+
|
|
1821
|
+
// Check if the result is just diagnostic information
|
|
1822
|
+
const isDiagnosticResult = result.includes('DEBUG:') ||
|
|
1823
|
+
result.includes('diagnostic') ||
|
|
1824
|
+
result.includes('No chat content found') ||
|
|
1825
|
+
result.includes('Text areas found: 0') ||
|
|
1826
|
+
result.includes('Static text found:') ||
|
|
1827
|
+
result.includes('Window count:') ||
|
|
1828
|
+
result.includes('Sample text:') ||
|
|
1829
|
+
result.includes('Could not read') ||
|
|
1830
|
+
result.includes('No readable text content detected');
|
|
1831
|
+
|
|
1832
|
+
this.logger.log(`🔍 Diagnostic detection check: isDiagnosticResult = ${isDiagnosticResult}`);
|
|
1833
|
+
this.logger.log(`🔍 Result contains 'DEBUG:': ${result.includes('DEBUG:')}`);
|
|
1834
|
+
this.logger.log(`🔍 Result contains 'Window count:': ${result.includes('Window count:')}`);
|
|
1835
|
+
|
|
1836
|
+
// For Cursor, always try CDP and clipboard methods as fallbacks
|
|
1837
|
+
if (ide === 'cursor') {
|
|
1838
|
+
this.logger.log('🔧 For Cursor, always trying CDP and clipboard methods as fallbacks...');
|
|
1839
|
+
|
|
1840
|
+
// Try CDP method
|
|
1841
|
+
try {
|
|
1842
|
+
this.logger.log('🔧 Attempting CDP method for Cursor...');
|
|
1843
|
+
const cdpResponse = await this.readCursorResponseViaCDP();
|
|
1844
|
+
if (cdpResponse && cdpResponse.length > 20) {
|
|
1845
|
+
this.logger.log('✅ Successfully read Cursor response via CDP');
|
|
1846
|
+
return cdpResponse;
|
|
1847
|
+
}
|
|
1848
|
+
} catch (error) {
|
|
1849
|
+
this.logger.log('⚠️ CDP method failed:', error.message);
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
// Try clipboard method
|
|
1853
|
+
try {
|
|
1854
|
+
this.logger.log('🔧 Attempting clipboard-based response detection...');
|
|
1855
|
+
const clipboardResponse = await this.readCursorResponseViaClipboard();
|
|
1856
|
+
if (clipboardResponse && clipboardResponse.length > 20) {
|
|
1857
|
+
this.logger.log('✅ Successfully read Cursor response via clipboard');
|
|
1858
|
+
return clipboardResponse;
|
|
1859
|
+
}
|
|
1860
|
+
} catch (error) {
|
|
1861
|
+
this.logger.log('⚠️ Clipboard method failed:', error.message);
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
if (isDiagnosticResult) {
|
|
1866
|
+
this.logger.log('⚠️ AppleScript returned diagnostic info, all methods failed...');
|
|
1867
|
+
return `Unable to read Cursor response - all methods failed. AppleScript diagnostic: ${result.substring(0, 200)}...`;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
// If we got a very short result, try a diagnostic approach
|
|
1871
|
+
if (result.trim().length <= 5) {
|
|
1872
|
+
this.logger.log('AppleScript returned very short result, trying diagnostic approach...');
|
|
1873
|
+
|
|
1874
|
+
const processName = ide === 'windsurf' ? 'Windsurf' : ide === 'antigravity' ? 'Antigravity' : 'Cursor';
|
|
1875
|
+
const diagnosticScript = `
|
|
1876
|
+
tell application "System Events"
|
|
1877
|
+
tell process "${processName}"
|
|
1878
|
+
set frontmost to true
|
|
1879
|
+
delay 1
|
|
1880
|
+
|
|
1881
|
+
-- Get window info
|
|
1882
|
+
set windowInfo to "Window: " & name of window 1 & "\\n"
|
|
1883
|
+
|
|
1884
|
+
-- Try to get element counts
|
|
1885
|
+
try
|
|
1886
|
+
set staticTexts to static text of window 1
|
|
1887
|
+
set groups to group of window 1
|
|
1888
|
+
set textAreas to text area of window 1
|
|
1889
|
+
set scrollAreas to scroll area of window 1
|
|
1890
|
+
set uiElements to every UI element of window 1
|
|
1891
|
+
|
|
1892
|
+
set elementInfo to "\\nStatic Texts: " & count of staticTexts & "\\n"
|
|
1893
|
+
set elementInfo to elementInfo & "Groups: " & count of groups & "\\n"
|
|
1894
|
+
set elementInfo to elementInfo & "Text Areas: " & count of textAreas & "\\n"
|
|
1895
|
+
set elementInfo to elementInfo & "Scroll Areas: " & count of scrollAreas & "\\n"
|
|
1896
|
+
set elementInfo to elementInfo & "UI Elements: " & count of uiElements & "\\n"
|
|
1897
|
+
|
|
1898
|
+
-- Try to get sample text from each type
|
|
1899
|
+
if count of staticTexts > 0 then
|
|
1900
|
+
try
|
|
1901
|
+
set sampleText to value of item 1 of staticTexts
|
|
1902
|
+
set elementInfo to elementInfo & "\\nSample static text: " & sampleText & "\\n"
|
|
1903
|
+
on error
|
|
1904
|
+
set elementInfo to elementInfo & "\\nCould not read sample static text\\n"
|
|
1905
|
+
end try
|
|
1906
|
+
end if
|
|
1907
|
+
|
|
1908
|
+
if count of textAreas > 0 then
|
|
1909
|
+
try
|
|
1910
|
+
set sampleText to value of item 1 of textAreas
|
|
1911
|
+
set elementInfo to elementInfo & "\\nSample text area: " & sampleText & "\\n"
|
|
1912
|
+
on error
|
|
1913
|
+
set elementInfo to elementInfo & "\\nCould not read sample text area\\n"
|
|
1914
|
+
end try
|
|
1915
|
+
end if
|
|
1916
|
+
|
|
1917
|
+
return windowInfo & elementInfo
|
|
1918
|
+
on error
|
|
1919
|
+
return windowInfo & "\\nCould not get element info"
|
|
1920
|
+
end try
|
|
1921
|
+
end tell
|
|
1922
|
+
end tell
|
|
1923
|
+
`;
|
|
1924
|
+
|
|
1925
|
+
const diagnosticResult = execSync(`osascript -e '${diagnosticScript}'`, { stdio: 'pipe', encoding: 'utf8' });
|
|
1926
|
+
this.logger.log('Diagnostic AppleScript result:', diagnosticResult);
|
|
1927
|
+
|
|
1928
|
+
if (diagnosticResult.length > 20) {
|
|
1929
|
+
return diagnosticResult.trim();
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
return result.trim();
|
|
1934
|
+
|
|
1935
|
+
} catch (error) {
|
|
1936
|
+
this.logger.log('AppleScript reading failed:', error.message);
|
|
1937
|
+
return 'Error: Could not read response via AppleScript';
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
/**
|
|
1942
|
+
* Detect quota warnings using AppleScript
|
|
1943
|
+
* @param {string} ide - The IDE name ('windsurf')
|
|
1944
|
+
* @returns {Promise<Object>} Quota detection result
|
|
1945
|
+
*/
|
|
1946
|
+
async detectQuotaWarning(ide) {
|
|
1947
|
+
if (ide !== 'windsurf') {
|
|
1948
|
+
return {
|
|
1949
|
+
hasQuotaWarning: false,
|
|
1950
|
+
note: 'AppleScript quota detection is only supported for Windsurf'
|
|
1951
|
+
};
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
this.logger.log('🔍 === APPLESCRIPT DETECTION START ===');
|
|
1955
|
+
this.logger.log('🔍 Starting enhanced AppleScript quota detection...');
|
|
1956
|
+
|
|
1957
|
+
try {
|
|
1958
|
+
// Test basic AppleScript execution first
|
|
1959
|
+
this.logger.log('🔍 Testing basic AppleScript execution...');
|
|
1960
|
+
const basicTest = execSync('osascript -e "return \\"test\\""', { stdio: 'pipe', encoding: 'utf8' });
|
|
1961
|
+
this.logger.log('✅ Basic AppleScript test passed:', basicTest.trim());
|
|
1962
|
+
} catch (basicError) {
|
|
1963
|
+
this.logger.log('❌ Basic AppleScript test failed:', basicError.message);
|
|
1964
|
+
return {
|
|
1965
|
+
hasQuotaWarning: false,
|
|
1966
|
+
error: 'AppleScript not available',
|
|
1967
|
+
note: 'Basic AppleScript test failed'
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
// Try a much simpler AppleScript first
|
|
1972
|
+
this.logger.log('🔍 Trying simple AppleScript approach...');
|
|
1973
|
+
const simpleAppleScript = `
|
|
1974
|
+
tell application "System Events"
|
|
1975
|
+
tell process "Windsurf"
|
|
1976
|
+
set frontmost to true
|
|
1977
|
+
delay 1
|
|
1978
|
+
|
|
1979
|
+
-- Method 1: Look for static text containing quota warnings
|
|
1980
|
+
try
|
|
1981
|
+
set allText to value of static text of window 1
|
|
1982
|
+
if allText contains "Not enough credits" or allText contains "Upgrade to a paid plan" or allText contains "switch to SWE-1-lite" then
|
|
1983
|
+
return "QUOTA_WARNING_DETECTED: " & allText
|
|
1984
|
+
end if
|
|
1985
|
+
on error
|
|
1986
|
+
-- Continue to next method
|
|
1987
|
+
end try
|
|
1988
|
+
|
|
1989
|
+
-- Method 2: Look for disabled text areas (quota warnings often disable chat input)
|
|
1990
|
+
try
|
|
1991
|
+
set textAreas to text area of window 1
|
|
1992
|
+
repeat with textArea in textAreas
|
|
1993
|
+
try
|
|
1994
|
+
if not enabled of textArea then
|
|
1995
|
+
return "QUOTA_INDICATOR_DETECTED: Disabled text area found"
|
|
1996
|
+
end if
|
|
1997
|
+
on error
|
|
1998
|
+
-- Continue to next text area
|
|
1999
|
+
end try
|
|
2000
|
+
end repeat
|
|
2001
|
+
on error
|
|
2002
|
+
-- Continue to next method
|
|
2003
|
+
end try
|
|
2004
|
+
|
|
2005
|
+
-- Method 3: Look for specific UI elements that indicate quota issues
|
|
2006
|
+
try
|
|
2007
|
+
set buttons to button of window 1
|
|
2008
|
+
repeat with btn in buttons
|
|
2009
|
+
try
|
|
2010
|
+
set buttonText to title of btn
|
|
2011
|
+
if buttonText contains "Upgrade" or buttonText contains "credits" or buttonText contains "quota" then
|
|
2012
|
+
return "QUOTA_UI_DETECTED: " & buttonText
|
|
2013
|
+
end if
|
|
2014
|
+
on error
|
|
2015
|
+
-- Continue to next button
|
|
2016
|
+
end try
|
|
2017
|
+
end repeat
|
|
2018
|
+
on error
|
|
2019
|
+
-- Continue to next method
|
|
2020
|
+
end try
|
|
2021
|
+
|
|
2022
|
+
-- Method 4: Look for groups that might contain quota information
|
|
2023
|
+
try
|
|
2024
|
+
set groups to group of window 1
|
|
2025
|
+
repeat with grp in groups
|
|
2026
|
+
try
|
|
2027
|
+
set groupText to value of static text of grp
|
|
2028
|
+
if groupText contains "credits" or groupText contains "quota" or groupText contains "upgrade" then
|
|
2029
|
+
return "QUOTA_GROUP_DETECTED: " & groupText
|
|
2030
|
+
end if
|
|
2031
|
+
on error
|
|
2032
|
+
-- Continue to next group
|
|
2033
|
+
end try
|
|
2034
|
+
end repeat
|
|
2035
|
+
on error
|
|
2036
|
+
-- Continue to next method
|
|
2037
|
+
end try
|
|
2038
|
+
|
|
2039
|
+
return "NO_QUOTA_WARNING_DETECTED"
|
|
2040
|
+
end tell
|
|
2041
|
+
end tell
|
|
2042
|
+
`;
|
|
2043
|
+
|
|
2044
|
+
this.logger.log('🔍 Simple AppleScript length:', simpleAppleScript.length);
|
|
2045
|
+
this.logger.log('🔍 Simple AppleScript preview:', simpleAppleScript.substring(0, 200) + '...');
|
|
2046
|
+
|
|
2047
|
+
try {
|
|
2048
|
+
this.logger.log('🔍 Executing simple AppleScript...');
|
|
2049
|
+
this.logger.log('🔍 AppleScript command length:', simpleAppleScript.length);
|
|
2050
|
+
|
|
2051
|
+
const result = execSync(`osascript -e '${simpleAppleScript}'`, { stdio: 'pipe', encoding: 'utf8' });
|
|
2052
|
+
this.logger.log('🔍 AppleScript execution completed');
|
|
2053
|
+
this.logger.log('🔍 AppleScript result length:', result.length);
|
|
2054
|
+
this.logger.log('🔍 AppleScript result:', result.trim());
|
|
2055
|
+
this.logger.log('🔍 AppleScript result starts with:', result.trim().substring(0, 100));
|
|
2056
|
+
|
|
2057
|
+
// Check for quota warnings in the result
|
|
2058
|
+
if (result.includes('QUOTA_WARNING_DETECTED')) {
|
|
2059
|
+
this.logger.log('❌ Windsurf quota warning detected via enhanced AppleScript');
|
|
2060
|
+
return {
|
|
2061
|
+
hasQuotaWarning: true,
|
|
2062
|
+
method: 'applescript-static-text',
|
|
2063
|
+
note: 'Windsurf quota warning detected via enhanced AppleScript detection'
|
|
2064
|
+
};
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
if (result.includes('QUOTA_INDICATOR_DETECTED')) {
|
|
2068
|
+
this.logger.log('❌ Windsurf quota indicator detected via enhanced AppleScript');
|
|
2069
|
+
return {
|
|
2070
|
+
hasQuotaWarning: true,
|
|
2071
|
+
method: 'applescript-disabled-input',
|
|
2072
|
+
note: 'Windsurf quota indicator detected via enhanced AppleScript (disabled chat input)'
|
|
2073
|
+
};
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
if (result.includes('QUOTA_UI_DETECTED') || result.includes('QUOTA_GROUP_DETECTED')) {
|
|
2077
|
+
this.logger.log('⚠️ Windsurf potential quota text detected via enhanced AppleScript');
|
|
2078
|
+
return {
|
|
2079
|
+
hasQuotaWarning: true,
|
|
2080
|
+
method: 'applescript-ui-elements',
|
|
2081
|
+
note: 'Windsurf potential quota text detected via enhanced AppleScript'
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
// Additional pattern matching for quota-related text
|
|
2086
|
+
const quotaPatterns = [
|
|
2087
|
+
'not enough credits',
|
|
2088
|
+
'upgrade to a paid plan',
|
|
2089
|
+
'switch to swe-1-lite',
|
|
2090
|
+
'quota exceeded',
|
|
2091
|
+
'credits exhausted',
|
|
2092
|
+
'payment required'
|
|
2093
|
+
];
|
|
2094
|
+
|
|
2095
|
+
const lowerResult = result.toLowerCase();
|
|
2096
|
+
for (const pattern of quotaPatterns) {
|
|
2097
|
+
if (lowerResult.includes(pattern)) {
|
|
2098
|
+
this.logger.log('❌ Windsurf quota warning detected via text pattern matching');
|
|
2099
|
+
return {
|
|
2100
|
+
hasQuotaWarning: true,
|
|
2101
|
+
method: 'applescript-pattern-matching',
|
|
2102
|
+
pattern: pattern,
|
|
2103
|
+
note: 'Windsurf quota warning detected via text pattern matching in AppleScript result'
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
this.logger.log('🔍 === APPLESCRIPT DETECTION END ===');
|
|
2109
|
+
this.logger.log('🔍 Since AppleScript cannot access web view, trying alternative methods...');
|
|
2110
|
+
|
|
2111
|
+
// Since AppleScript cannot access web view content, return a note about the limitation
|
|
2112
|
+
return {
|
|
2113
|
+
hasQuotaWarning: false,
|
|
2114
|
+
method: 'applescript-limited',
|
|
2115
|
+
note: 'Windsurf quota warning is visible in UI but not detectable via AppleScript'
|
|
2116
|
+
};
|
|
2117
|
+
|
|
2118
|
+
} catch (error) {
|
|
2119
|
+
this.logger.log('❌ AppleScript error:', error.message);
|
|
2120
|
+
return {
|
|
2121
|
+
hasQuotaWarning: false,
|
|
2122
|
+
error: error.message,
|
|
2123
|
+
note: 'AppleScript cannot access web view content - Windsurf does not support CDP'
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
/**
|
|
2129
|
+
* Read Cursor response via Chrome DevTools Protocol (CDP)
|
|
2130
|
+
* @returns {Promise<string>} The chat response text
|
|
2131
|
+
*/
|
|
2132
|
+
async readCursorResponseViaCDP() {
|
|
2133
|
+
try {
|
|
2134
|
+
this.logger.log('🔧 Attempting CDP connection to Cursor on port 9225...');
|
|
2135
|
+
|
|
2136
|
+
// Import CDP dynamically to avoid issues in VSCode extension context
|
|
2137
|
+
const CDP = require('chrome-remote-interface');
|
|
2138
|
+
|
|
2139
|
+
// List available targets
|
|
2140
|
+
const targets = await CDP.List({ port: 9225 });
|
|
2141
|
+
this.logger.log(`🔧 Found ${targets.length} CDP targets`);
|
|
2142
|
+
|
|
2143
|
+
// Find the main workbench target (not settings)
|
|
2144
|
+
const workbench = targets.find(t =>
|
|
2145
|
+
t.title !== 'Cursor Settings' &&
|
|
2146
|
+
t.type === 'page' &&
|
|
2147
|
+
t.url && t.url.includes('workbench')
|
|
2148
|
+
) || targets[0];
|
|
2149
|
+
|
|
2150
|
+
if (!workbench) {
|
|
2151
|
+
throw new Error('No suitable Cursor workbench target found');
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
this.logger.log(`🔧 Connecting to target: ${workbench.title}`);
|
|
2155
|
+
|
|
2156
|
+
// Connect to the target
|
|
2157
|
+
const client = await CDP({ port: 9225, target: workbench });
|
|
2158
|
+
const { Runtime, DOM } = client;
|
|
2159
|
+
|
|
2160
|
+
// Enable required domains
|
|
2161
|
+
await Runtime.enable();
|
|
2162
|
+
await DOM.enable();
|
|
2163
|
+
|
|
2164
|
+
this.logger.log('🔧 CDP connection established, searching for chat content...');
|
|
2165
|
+
|
|
2166
|
+
// Try multiple selectors to find chat content
|
|
2167
|
+
const selectors = [
|
|
2168
|
+
'.chat-content',
|
|
2169
|
+
'.chat-messages',
|
|
2170
|
+
'.response-content',
|
|
2171
|
+
'.ai-response',
|
|
2172
|
+
'.message-content',
|
|
2173
|
+
'[data-testid="chat-message"]',
|
|
2174
|
+
'.markdown-content',
|
|
2175
|
+
'.prose',
|
|
2176
|
+
'.chat-panel',
|
|
2177
|
+
'.sidebar-panel'
|
|
2178
|
+
];
|
|
2179
|
+
|
|
2180
|
+
for (const selector of selectors) {
|
|
2181
|
+
try {
|
|
2182
|
+
this.logger.log(`🔧 Trying selector: ${selector}`);
|
|
2183
|
+
|
|
2184
|
+
// Get document root
|
|
2185
|
+
const { root } = await DOM.getDocument();
|
|
2186
|
+
|
|
2187
|
+
// Search for elements with the selector
|
|
2188
|
+
const { nodeIds } = await DOM.querySelectorAll({ nodeId: root.nodeId, selector });
|
|
2189
|
+
|
|
2190
|
+
if (nodeIds.length > 0) {
|
|
2191
|
+
this.logger.log(`🔧 Found ${nodeIds.length} elements with selector: ${selector}`);
|
|
2192
|
+
|
|
2193
|
+
// Get text content from the first matching element
|
|
2194
|
+
const { nodeId } = await DOM.describeNode({ nodeId: nodeIds[0] });
|
|
2195
|
+
const { outerHTML } = await DOM.getOuterHTML({ nodeId });
|
|
2196
|
+
|
|
2197
|
+
// Extract text content (remove HTML tags)
|
|
2198
|
+
const textContent = outerHTML.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
|
|
2199
|
+
|
|
2200
|
+
if (textContent && textContent.length > 20) {
|
|
2201
|
+
this.logger.log(`✅ Successfully extracted chat content via CDP: ${textContent.substring(0, 100)}...`);
|
|
2202
|
+
await client.close();
|
|
2203
|
+
return textContent;
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
} catch (error) {
|
|
2207
|
+
this.logger.log(`⚠️ Selector ${selector} failed: ${error.message}`);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
// If no specific selectors work, try to get all text content
|
|
2212
|
+
this.logger.log('🔧 Trying to get all text content from the page...');
|
|
2213
|
+
|
|
2214
|
+
const { root } = await DOM.getDocument();
|
|
2215
|
+
const { outerHTML } = await DOM.getOuterHTML({ nodeId: root.nodeId });
|
|
2216
|
+
|
|
2217
|
+
// Extract text content and look for AI responses
|
|
2218
|
+
const textContent = outerHTML.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
|
|
2219
|
+
|
|
2220
|
+
// Look for patterns that indicate AI responses
|
|
2221
|
+
const aiResponsePatterns = [
|
|
2222
|
+
/(?:AI|Assistant|Claude|GPT|Response):\s*([^]*?)(?=\n\n|\n[A-Z]|$)/gi,
|
|
2223
|
+
/(?:Here|This|The answer|The solution):\s*([^]*?)(?=\n\n|\n[A-Z]|$)/gi
|
|
2224
|
+
];
|
|
2225
|
+
|
|
2226
|
+
for (const pattern of aiResponsePatterns) {
|
|
2227
|
+
const matches = textContent.match(pattern);
|
|
2228
|
+
if (matches && matches.length > 0) {
|
|
2229
|
+
const response = matches[matches.length - 1]; // Get the latest response
|
|
2230
|
+
if (response && response.length > 20) {
|
|
2231
|
+
this.logger.log(`✅ Found AI response via CDP pattern matching: ${response.substring(0, 100)}...`);
|
|
2232
|
+
await client.close();
|
|
2233
|
+
return response;
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
await client.close();
|
|
2239
|
+
throw new Error('No chat content found via CDP');
|
|
2240
|
+
|
|
2241
|
+
} catch (error) {
|
|
2242
|
+
this.logger.log(`❌ CDP method failed: ${error.message}`);
|
|
2243
|
+
throw error;
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
/**
|
|
2248
|
+
* Read Cursor response via clipboard (select all and copy)
|
|
2249
|
+
* @returns {Promise<string>} The chat response text
|
|
2250
|
+
*/
|
|
2251
|
+
async readCursorResponseViaClipboard() {
|
|
2252
|
+
try {
|
|
2253
|
+
this.logger.log('🔧 Attempting clipboard-based response detection...');
|
|
2254
|
+
|
|
2255
|
+
// AppleScript to select all content in Cursor and copy to clipboard
|
|
2256
|
+
const clipboardScript = `
|
|
2257
|
+
tell application "System Events"
|
|
2258
|
+
tell process "Cursor"
|
|
2259
|
+
set frontmost to true
|
|
2260
|
+
delay 1
|
|
2261
|
+
|
|
2262
|
+
-- Try to focus on the chat area first without selecting all content
|
|
2263
|
+
try
|
|
2264
|
+
-- Just try to copy without selecting all (safer approach)
|
|
2265
|
+
key code 8 using {command down} -- Cmd+C to copy
|
|
2266
|
+
delay 0.5
|
|
2267
|
+
on error
|
|
2268
|
+
-- If that fails, return empty response
|
|
2269
|
+
return ""
|
|
2270
|
+
end try
|
|
2271
|
+
end tell
|
|
2272
|
+
end tell
|
|
2273
|
+
`;
|
|
2274
|
+
|
|
2275
|
+
// Use file-based execution for clipboard script too
|
|
2276
|
+
const tempFile = join(tmpdir(), `clipboard_${Date.now()}.scpt`);
|
|
2277
|
+
try {
|
|
2278
|
+
writeFileSync(tempFile, clipboardScript, 'utf8');
|
|
2279
|
+
execSync(`osascript "${tempFile}"`, { stdio: 'pipe' });
|
|
2280
|
+
} finally {
|
|
2281
|
+
try {
|
|
2282
|
+
unlinkSync(tempFile);
|
|
2283
|
+
} catch (cleanupError) {
|
|
2284
|
+
this.logger.log(`⚠️ Failed to cleanup clipboard temp file: ${cleanupError.message}`);
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
// Read from clipboard
|
|
2289
|
+
const clipboardScript2 = `
|
|
2290
|
+
the clipboard
|
|
2291
|
+
`;
|
|
2292
|
+
|
|
2293
|
+
const tempFile2 = join(tmpdir(), `clipboard_read_${Date.now()}.scpt`);
|
|
2294
|
+
try {
|
|
2295
|
+
writeFileSync(tempFile2, clipboardScript2, 'utf8');
|
|
2296
|
+
const clipboardContent = execSync(`osascript "${tempFile2}"`, { stdio: 'pipe', encoding: 'utf8' });
|
|
2297
|
+
|
|
2298
|
+
if (clipboardContent && clipboardContent.trim().length > 20) {
|
|
2299
|
+
this.logger.log(`✅ Successfully read clipboard content: ${clipboardContent.substring(0, 100)}...`);
|
|
2300
|
+
return clipboardContent.trim();
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
throw new Error('Clipboard content is empty or too short');
|
|
2304
|
+
} finally {
|
|
2305
|
+
try {
|
|
2306
|
+
unlinkSync(tempFile2);
|
|
2307
|
+
} catch (cleanupError) {
|
|
2308
|
+
this.logger.log(`⚠️ Failed to cleanup clipboard read temp file: ${cleanupError.message}`);
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
} catch (error) {
|
|
2313
|
+
this.logger.log(`❌ Clipboard method failed: ${error.message}`);
|
|
2314
|
+
throw error;
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
/**
|
|
2319
|
+
* Close Claude CLI terminal windows
|
|
2320
|
+
* @returns {Promise<Object>} Result object with success status and details
|
|
2321
|
+
*/
|
|
2322
|
+
async closeClaudeCLI() {
|
|
2323
|
+
try {
|
|
2324
|
+
this.logger.log('🔄 [Claude] Closing Claude CLI terminal windows...');
|
|
2325
|
+
|
|
2326
|
+
const script = `
|
|
2327
|
+
tell application "Terminal"
|
|
2328
|
+
activate
|
|
2329
|
+
delay 1
|
|
2330
|
+
|
|
2331
|
+
-- Find all terminal windows with Claude CLI running
|
|
2332
|
+
set claudeWindows to {}
|
|
2333
|
+
repeat with w in windows
|
|
2334
|
+
try
|
|
2335
|
+
set windowName to name of w
|
|
2336
|
+
if windowName contains "claude" or windowName contains "Claude" then
|
|
2337
|
+
set end of claudeWindows to w
|
|
2338
|
+
end if
|
|
2339
|
+
end try
|
|
2340
|
+
end repeat
|
|
2341
|
+
|
|
2342
|
+
-- Close Claude terminal windows
|
|
2343
|
+
if (count of claudeWindows) > 0 then
|
|
2344
|
+
repeat with w in claudeWindows
|
|
2345
|
+
try
|
|
2346
|
+
close w
|
|
2347
|
+
delay 0.5
|
|
2348
|
+
end try
|
|
2349
|
+
end repeat
|
|
2350
|
+
return "Closed " & (count of claudeWindows) & " Claude terminal windows"
|
|
2351
|
+
else
|
|
2352
|
+
return "No Claude terminal windows found to close"
|
|
2353
|
+
end if
|
|
2354
|
+
end tell
|
|
2355
|
+
`;
|
|
2356
|
+
|
|
2357
|
+
// Create a temporary AppleScript file
|
|
2358
|
+
const tempScriptPath = join(tmpdir(), `close_claude_${Date.now()}.scpt`);
|
|
2359
|
+
writeFileSync(tempScriptPath, script);
|
|
2360
|
+
|
|
2361
|
+
// Execute the AppleScript file
|
|
2362
|
+
this.logger.log('🔧 Executing AppleScript to close Claude CLI terminal windows...');
|
|
2363
|
+
const result = execSync(`osascript "${tempScriptPath}"`, {
|
|
2364
|
+
encoding: 'utf8',
|
|
2365
|
+
timeout: 10000
|
|
2366
|
+
});
|
|
2367
|
+
|
|
2368
|
+
// Clean up the temporary file
|
|
2369
|
+
try {
|
|
2370
|
+
unlinkSync(tempScriptPath);
|
|
2371
|
+
} catch (cleanupError) {
|
|
2372
|
+
this.logger.log(`⚠️ Failed to cleanup temp file: ${cleanupError.message}`);
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
this.logger.log(`✅ Claude CLI close result: ${result.trim()}`);
|
|
2376
|
+
return {
|
|
2377
|
+
success: true,
|
|
2378
|
+
message: result.trim(),
|
|
2379
|
+
method: 'applescript',
|
|
2380
|
+
action: 'close'
|
|
2381
|
+
};
|
|
2382
|
+
} catch (error) {
|
|
2383
|
+
this.logger.log(`⚠️ Error closing Claude CLI: ${error.message}`);
|
|
2384
|
+
return {
|
|
2385
|
+
success: false,
|
|
2386
|
+
error: error.message,
|
|
2387
|
+
method: 'applescript'
|
|
2388
|
+
};
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
/**
|
|
2393
|
+
* Close previous chat thread in an IDE to prevent conflicts
|
|
2394
|
+
* @param {string} ide - The IDE name ('cursor', 'vscode', 'windsurf', 'claude')
|
|
2395
|
+
* @returns {Promise<Object>} Result object with success status and details
|
|
2396
|
+
*/
|
|
2397
|
+
async closePreviousChatThread(ide) {
|
|
2398
|
+
if (!ide || typeof ide !== 'string') {
|
|
2399
|
+
return {
|
|
2400
|
+
success: false,
|
|
2401
|
+
error: 'Invalid IDE parameter',
|
|
2402
|
+
method: 'applescript'
|
|
2403
|
+
};
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
const ideName = ide.toLowerCase();
|
|
2407
|
+
|
|
2408
|
+
// Handle Claude CLI separately since it's a terminal application
|
|
2409
|
+
if (ideName === 'claude') {
|
|
2410
|
+
return await this.closeClaudeCLI();
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
if (!['cursor', 'vscode', 'windsurf'].includes(ideName)) {
|
|
2414
|
+
return {
|
|
2415
|
+
success: false,
|
|
2416
|
+
error: 'Unsupported IDE for thread closure',
|
|
2417
|
+
method: 'applescript'
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
try {
|
|
2422
|
+
this.logger.log(`🔄 [${ideName}] Closing previous chat thread...`);
|
|
2423
|
+
|
|
2424
|
+
let script = '';
|
|
2425
|
+
switch (ideName) {
|
|
2426
|
+
case 'cursor':
|
|
2427
|
+
script = `
|
|
2428
|
+
tell application "Cursor"
|
|
2429
|
+
activate
|
|
2430
|
+
delay 1
|
|
2431
|
+
end tell
|
|
2432
|
+
|
|
2433
|
+
tell application "System Events"
|
|
2434
|
+
tell process "Cursor"
|
|
2435
|
+
set frontmost to true
|
|
2436
|
+
delay 1
|
|
2437
|
+
|
|
2438
|
+
-- Close previous chat thread by starting a new conversation
|
|
2439
|
+
try
|
|
2440
|
+
-- Open Command Palette (Cmd+Shift+P)
|
|
2441
|
+
key code 35 using {command down, shift down} -- Cmd+Shift+P
|
|
2442
|
+
delay 0.5
|
|
2443
|
+
|
|
2444
|
+
-- Type "New Chat" or "Start New Conversation"
|
|
2445
|
+
keystroke "New Chat"
|
|
2446
|
+
delay 0.5
|
|
2447
|
+
|
|
2448
|
+
-- Press Enter to start new conversation
|
|
2449
|
+
key code 36 -- Enter
|
|
2450
|
+
delay 1
|
|
2451
|
+
|
|
2452
|
+
return "Previous chat thread closed in Cursor"
|
|
2453
|
+
on error
|
|
2454
|
+
-- Fallback: just return success without clearing anything
|
|
2455
|
+
return "Previous chat thread closed in Cursor (fallback)"
|
|
2456
|
+
end try
|
|
2457
|
+
end tell
|
|
2458
|
+
end tell
|
|
2459
|
+
`;
|
|
2460
|
+
break;
|
|
2461
|
+
|
|
2462
|
+
case 'vscode':
|
|
2463
|
+
script = `
|
|
2464
|
+
tell application "Visual Studio Code"
|
|
2465
|
+
activate
|
|
2466
|
+
delay 1.5
|
|
2467
|
+
end tell
|
|
2468
|
+
|
|
2469
|
+
tell application "System Events"
|
|
2470
|
+
tell process "Visual Studio Code"
|
|
2471
|
+
set frontmost to true
|
|
2472
|
+
delay 1.5
|
|
2473
|
+
|
|
2474
|
+
-- Close previous chat thread in VS Code
|
|
2475
|
+
try
|
|
2476
|
+
-- Open Command Palette (Cmd+Shift+P)
|
|
2477
|
+
key code 35 using {command down, shift down} -- Cmd+Shift+P
|
|
2478
|
+
delay 1.0
|
|
2479
|
+
|
|
2480
|
+
-- Type "New Chat" or "Start New Conversation"
|
|
2481
|
+
keystroke "New Chat"
|
|
2482
|
+
delay 1.0
|
|
2483
|
+
|
|
2484
|
+
-- Press Enter to start new conversation
|
|
2485
|
+
key code 36 -- Enter
|
|
2486
|
+
delay 1.5
|
|
2487
|
+
|
|
2488
|
+
return "Previous chat thread closed in VS Code"
|
|
2489
|
+
on error
|
|
2490
|
+
-- Fallback: just return success without clearing anything
|
|
2491
|
+
return "Previous chat thread closed in VS Code (fallback)"
|
|
2492
|
+
end try
|
|
2493
|
+
end tell
|
|
2494
|
+
end tell
|
|
2495
|
+
`;
|
|
2496
|
+
break;
|
|
2497
|
+
|
|
2498
|
+
case 'windsurf':
|
|
2499
|
+
script = `
|
|
2500
|
+
tell application "Windsurf"
|
|
2501
|
+
activate
|
|
2502
|
+
delay 1
|
|
2503
|
+
end tell
|
|
2504
|
+
|
|
2505
|
+
tell application "System Events"
|
|
2506
|
+
tell process "Windsurf"
|
|
2507
|
+
set frontmost to true
|
|
2508
|
+
delay 1
|
|
2509
|
+
|
|
2510
|
+
-- Close previous chat thread in Windsurf
|
|
2511
|
+
try
|
|
2512
|
+
-- Open Command Palette (Cmd+Shift+P)
|
|
2513
|
+
key code 35 using {command down, shift down} -- Cmd+Shift+P
|
|
2514
|
+
delay 0.5
|
|
2515
|
+
|
|
2516
|
+
-- Type "New Chat" or "Start New Conversation"
|
|
2517
|
+
keystroke "New Chat"
|
|
2518
|
+
delay 0.5
|
|
2519
|
+
|
|
2520
|
+
-- Press Enter to start new conversation
|
|
2521
|
+
key code 36 -- Enter
|
|
2522
|
+
delay 1
|
|
2523
|
+
|
|
2524
|
+
return "Previous chat thread closed in Windsurf"
|
|
2525
|
+
on error
|
|
2526
|
+
-- Fallback: just return success without clearing anything
|
|
2527
|
+
return "Previous chat thread closed in Windsurf (fallback)"
|
|
2528
|
+
end try
|
|
2529
|
+
end tell
|
|
2530
|
+
end tell
|
|
2531
|
+
`;
|
|
2532
|
+
break;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
// Create a temporary AppleScript file
|
|
2536
|
+
const tempScriptPath = join(tmpdir(), `close_thread_${Date.now()}.scpt`);
|
|
2537
|
+
writeFileSync(tempScriptPath, script);
|
|
2538
|
+
|
|
2539
|
+
// Execute the AppleScript file
|
|
2540
|
+
this.logger.log(`🔧 Executing AppleScript to close previous thread in ${ideName}...`);
|
|
2541
|
+
const result = execSync(`osascript "${tempScriptPath}"`, {
|
|
2542
|
+
encoding: 'utf8',
|
|
2543
|
+
timeout: 10000
|
|
2544
|
+
});
|
|
2545
|
+
|
|
2546
|
+
// Clean up the temporary file
|
|
2547
|
+
try {
|
|
2548
|
+
unlinkSync(tempScriptPath);
|
|
2549
|
+
} catch (cleanupError) {
|
|
2550
|
+
this.logger.log(`⚠️ Failed to cleanup temp file: ${cleanupError.message}`);
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
this.logger.log(`✅ Previous chat thread closed in ${ideName}: ${result.trim()}`);
|
|
2554
|
+
return {
|
|
2555
|
+
success: true,
|
|
2556
|
+
message: result.trim(),
|
|
2557
|
+
method: 'applescript'
|
|
2558
|
+
};
|
|
2559
|
+
|
|
2560
|
+
} catch (error) {
|
|
2561
|
+
this.logger.log(`❌ Failed to close previous chat thread in ${ideName}: ${error.message}`);
|
|
2562
|
+
return {
|
|
2563
|
+
success: false,
|
|
2564
|
+
error: error.message,
|
|
2565
|
+
method: 'applescript'
|
|
2566
|
+
};
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
/**
|
|
2571
|
+
* Send text to an IDE with automatic thread closure
|
|
2572
|
+
* @param {string} text - The text to send
|
|
2573
|
+
* @param {string} ide - The IDE name ('cursor', 'vscode', 'windsurf')
|
|
2574
|
+
* @returns {Promise<Object>} Result object with success status and details
|
|
2575
|
+
*/
|
|
2576
|
+
async sendTextWithThreadClosure(text, ide) {
|
|
2577
|
+
if (!text || typeof text !== 'string' || !text.trim()) {
|
|
2578
|
+
return {
|
|
2579
|
+
success: false,
|
|
2580
|
+
error: 'Invalid text parameter',
|
|
2581
|
+
method: 'applescript'
|
|
2582
|
+
};
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
if (!ide || typeof ide !== 'string') {
|
|
2586
|
+
return {
|
|
2587
|
+
success: false,
|
|
2588
|
+
error: 'Invalid IDE parameter',
|
|
2589
|
+
method: 'applescript'
|
|
2590
|
+
};
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
try {
|
|
2594
|
+
this.logger.log(`🔄 [${ide}] Sending text with thread closure...`);
|
|
2595
|
+
|
|
2596
|
+
// First, try to close the previous chat thread
|
|
2597
|
+
const closeResult = await this.closePreviousChatThread(ide);
|
|
2598
|
+
|
|
2599
|
+
// Wait a moment for the thread closure to complete
|
|
2600
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
2601
|
+
|
|
2602
|
+
// Then send the new message
|
|
2603
|
+
const sendResult = await this.sendText(text, ide);
|
|
2604
|
+
|
|
2605
|
+
// Combine the results
|
|
2606
|
+
if (sendResult.success) {
|
|
2607
|
+
return {
|
|
2608
|
+
success: true,
|
|
2609
|
+
message: sendResult.message,
|
|
2610
|
+
method: sendResult.method,
|
|
2611
|
+
threadClosure: closeResult.success ? 'Previous thread closed' : 'Thread closure failed',
|
|
2612
|
+
warning: !closeResult.success ? `Failed to close previous thread: ${closeResult.error}` : undefined
|
|
2613
|
+
};
|
|
2614
|
+
} else {
|
|
2615
|
+
return {
|
|
2616
|
+
success: false,
|
|
2617
|
+
error: sendResult.error,
|
|
2618
|
+
method: sendResult.method,
|
|
2619
|
+
threadClosure: closeResult.success ? 'Previous thread closed' : 'Thread closure failed'
|
|
2620
|
+
};
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
} catch (error) {
|
|
2624
|
+
this.logger.log(`❌ Failed to send text with thread closure to ${ide}: ${error.message}`);
|
|
2625
|
+
return {
|
|
2626
|
+
success: false,
|
|
2627
|
+
error: error.message,
|
|
2628
|
+
method: 'applescript'
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
/**
|
|
2634
|
+
* Programmatically sets the state of the Gemini "Code Assistance" feature in VS Code's settings.json.
|
|
2635
|
+
* This is more reliable than UI automation.
|
|
2636
|
+
* @param {boolean} enable - True to enable, false to disable.
|
|
2637
|
+
* @returns {Promise<Object>} Result object with success status and details.
|
|
2638
|
+
* @private
|
|
2639
|
+
*/
|
|
2640
|
+
async _setGeminiAgentState(enable) {
|
|
2641
|
+
const ideName = 'VS Code';
|
|
2642
|
+
const action = enable ? 'Enabling' : 'Disabling';
|
|
2643
|
+
this.logger.log(`🚀 [${ideName}] ${action} Gemini Code Assistance by editing settings.json...`);
|
|
2644
|
+
try {
|
|
2645
|
+
const fs = require('fs');
|
|
2646
|
+
const path = require('path');
|
|
2647
|
+
const os = require('os');
|
|
2648
|
+
|
|
2649
|
+
// Determine the path to settings.json based on the OS
|
|
2650
|
+
const homeDir = os.homedir();
|
|
2651
|
+
let settingsPath;
|
|
2652
|
+
if (this.platform === 'darwin') {
|
|
2653
|
+
settingsPath = path.join(homeDir, 'Library', 'Application Support', 'Code', 'User', 'settings.json');
|
|
2654
|
+
} else if (this.platform === 'win32') {
|
|
2655
|
+
settingsPath = path.join(homeDir, 'AppData', 'Roaming', 'Code', 'User', 'settings.json');
|
|
2656
|
+
} else { // Linux
|
|
2657
|
+
settingsPath = path.join(homeDir, '.config', 'Code', 'User', 'settings.json');
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
this.logger.log(`📝 Located settings.json at: ${settingsPath}`);
|
|
2661
|
+
|
|
2662
|
+
if (!fs.existsSync(settingsPath)) {
|
|
2663
|
+
throw new Error(`VS Code settings.json not found at ${settingsPath}`);
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
// Read and parse the settings file, handling comments
|
|
2667
|
+
const settingsContent = fs.readFileSync(settingsPath, 'utf8');
|
|
2668
|
+
// A simple regex to strip comments before parsing
|
|
2669
|
+
const jsonContent = settingsContent.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g) => g ? "" : m);
|
|
2670
|
+
const settings = JSON.parse(jsonContent);
|
|
2671
|
+
|
|
2672
|
+
// Update the setting
|
|
2673
|
+
const settingKey = 'gemini.codeAssistance';
|
|
2674
|
+
settings[settingKey] = enable;
|
|
2675
|
+
|
|
2676
|
+
// Write the updated settings back to the file
|
|
2677
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
2678
|
+
|
|
2679
|
+
this.logger.log(`✅ [${ideName}] Successfully set "${settingKey}": ${enable} in settings.json.`);
|
|
2680
|
+
return {
|
|
2681
|
+
success: true,
|
|
2682
|
+
method: 'settings.json',
|
|
2683
|
+
message: `Gemini Code Assistance ${action.toLowerCase()}d successfully.`
|
|
2684
|
+
};
|
|
2685
|
+
} catch (error) {
|
|
2686
|
+
this.logger.error(`❌ [${ideName}] Error toggling Gemini Agent via settings.json:`, error.message);
|
|
2687
|
+
return {
|
|
2688
|
+
success: false,
|
|
2689
|
+
error: error.message,
|
|
2690
|
+
method: 'settings.json'
|
|
2691
|
+
};
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
|
|
2696
|
+
/**
|
|
2697
|
+
* Check if we're running within a VS Code extension context
|
|
2698
|
+
* @returns {boolean} True if running in VS Code extension
|
|
2699
|
+
*/
|
|
2700
|
+
isRunningInVSCodeExtension() {
|
|
2701
|
+
try {
|
|
2702
|
+
// Check for VS Code extension environment variables
|
|
2703
|
+
return !!(process.env.VSCODE_PID ||
|
|
2704
|
+
process.env.VSCODE_INJECTION ||
|
|
2705
|
+
process.env.VSCODE_IPC_HOOK_CLI ||
|
|
2706
|
+
(typeof require !== 'undefined' && require.main && require.main.filename.includes('vscode')));
|
|
2707
|
+
} catch (error) {
|
|
2708
|
+
this.logger.log('⚠️ Could not determine VS Code extension context:', error.message);
|
|
2709
|
+
return false;
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
/**
|
|
2714
|
+
* Send text to Windsurf extension within VS Code
|
|
2715
|
+
* @param {string} text - The text to send
|
|
2716
|
+
* @returns {Promise<Object>} Result object with success status
|
|
2717
|
+
*/
|
|
2718
|
+
async sendTextToWindsurfExtension(text) {
|
|
2719
|
+
try {
|
|
2720
|
+
this.logger.log('🚀 Sending message to Windsurf extension via VS Code commands...');
|
|
2721
|
+
|
|
2722
|
+
// Since we're in the core package, we can't directly use vscode.commands
|
|
2723
|
+
// Instead, we'll use AppleScript to interact with VS Code
|
|
2724
|
+
const appleScript = `
|
|
2725
|
+
tell application "System Events"
|
|
2726
|
+
tell process "Visual Studio Code"
|
|
2727
|
+
set frontmost to true
|
|
2728
|
+
delay 1
|
|
2729
|
+
|
|
2730
|
+
-- Step 1: Open Command Palette (Cmd+Shift+P)
|
|
2731
|
+
key code 35 using {command down, shift down}
|
|
2732
|
+
delay 0.5
|
|
2733
|
+
|
|
2734
|
+
-- Step 2: Type "Windsurf: Open Chat"
|
|
2735
|
+
keystroke "Windsurf: Open Chat"
|
|
2736
|
+
delay 0.5
|
|
2737
|
+
|
|
2738
|
+
-- Step 3: Press Enter to execute the command
|
|
2739
|
+
key code 36
|
|
2740
|
+
delay 1.5
|
|
2741
|
+
|
|
2742
|
+
-- Step 4: Try to start a new chat session
|
|
2743
|
+
try
|
|
2744
|
+
-- Open Command Palette again
|
|
2745
|
+
key code 35 using {command down, shift down}
|
|
2746
|
+
delay 0.5
|
|
2747
|
+
|
|
2748
|
+
-- Type the new chat command
|
|
2749
|
+
keystroke "extension.windSurf.newChat"
|
|
2750
|
+
delay 0.5
|
|
2751
|
+
|
|
2752
|
+
-- Press Enter
|
|
2753
|
+
key code 36
|
|
2754
|
+
delay 1
|
|
2755
|
+
on error
|
|
2756
|
+
-- Continue if new chat command fails
|
|
2757
|
+
end try
|
|
2758
|
+
|
|
2759
|
+
-- Step 5: Send the message
|
|
2760
|
+
keystroke "${text.replace(/"/g, '\\"')}"
|
|
2761
|
+
delay 0.5
|
|
2762
|
+
|
|
2763
|
+
-- Step 6: Submit the message (Enter)
|
|
2764
|
+
key code 36
|
|
2765
|
+
delay 0.5
|
|
2766
|
+
|
|
2767
|
+
return "Message sent to Windsurf extension"
|
|
2768
|
+
end tell
|
|
2769
|
+
end tell
|
|
2770
|
+
`;
|
|
2771
|
+
|
|
2772
|
+
// Use file-based execution for AppleScript
|
|
2773
|
+
const tempFile = join(tmpdir(), `windsurf_extension_${Date.now()}.scpt`);
|
|
2774
|
+
try {
|
|
2775
|
+
writeFileSync(tempFile, appleScript, 'utf8');
|
|
2776
|
+
execSync(`osascript "${tempFile}"`, { stdio: 'pipe' });
|
|
2777
|
+
this.logger.log('✅ Message sent to Windsurf extension successfully');
|
|
2778
|
+
return {
|
|
2779
|
+
success: true,
|
|
2780
|
+
method: 'applescript-vscode-commands',
|
|
2781
|
+
message: `Message sent to Windsurf extension: ${text}`,
|
|
2782
|
+
note: 'Message sent via AppleScript automation to VS Code Windsurf extension'
|
|
2783
|
+
};
|
|
2784
|
+
} finally {
|
|
2785
|
+
try {
|
|
2786
|
+
unlinkSync(tempFile);
|
|
2787
|
+
} catch (cleanupError) {
|
|
2788
|
+
this.logger.log(`⚠️ Failed to cleanup temp file: ${cleanupError.message}`);
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
} catch (error) {
|
|
2793
|
+
this.logger.log('❌ Failed to send message to Windsurf extension:', error);
|
|
2794
|
+
return {
|
|
2795
|
+
success: false,
|
|
2796
|
+
error: `Failed to send message to Windsurf extension: ${error.message}`,
|
|
2797
|
+
method: 'applescript-vscode-commands'
|
|
2798
|
+
};
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
module.exports = AppleScriptManager;
|