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.
Files changed (54) hide show
  1. package/.babelrc +13 -0
  2. package/README.md +28 -0
  3. package/__tests__/applescript-manager-claude-fix.test.js +286 -0
  4. package/__tests__/requirement-2-auto-start-looping.test.js +69 -0
  5. package/__tests__/requirement-3-auto-start-looping.test.js +69 -0
  6. package/__tests__/requirement-4-auto-start-looping.test.js +69 -0
  7. package/__tests__/requirement-6-auto-start-looping.test.js +73 -0
  8. package/__tests__/requirement-7-status-tracking.test.js +332 -0
  9. package/jest.config.js +18 -0
  10. package/jest.setup.js +12 -0
  11. package/package.json +46 -0
  12. package/src/auth/access-denied.html +119 -0
  13. package/src/auth/shared-auth-storage.js +230 -0
  14. package/src/autonomous-mode/feature-implementer.cjs +70 -0
  15. package/src/autonomous-mode/feature-implementer.js +425 -0
  16. package/src/chat-management/chat-manager.cjs +71 -0
  17. package/src/chat-management/chat-manager.js +342 -0
  18. package/src/ide-integration/__tests__/applescript-manager-thread-closure.test.js +227 -0
  19. package/src/ide-integration/aider-cli-manager.cjs +850 -0
  20. package/src/ide-integration/applescript-diagnostics.js +0 -0
  21. package/src/ide-integration/applescript-manager.cjs +1088 -0
  22. package/src/ide-integration/applescript-manager.js +2803 -0
  23. package/src/ide-integration/applescript-open-apps.js +0 -0
  24. package/src/ide-integration/applescript-read-response.js +0 -0
  25. package/src/ide-integration/applescript-send-text.js +0 -0
  26. package/src/ide-integration/applescript-thread-closure.js +0 -0
  27. package/src/ide-integration/applescript-utils.js +306 -0
  28. package/src/ide-integration/cdp-manager.cjs +221 -0
  29. package/src/ide-integration/cdp-manager.js +321 -0
  30. package/src/ide-integration/claude-code-cli-manager.cjs +301 -0
  31. package/src/ide-integration/cline-cli-manager.cjs +2252 -0
  32. package/src/ide-integration/continue-cli-manager.js +431 -0
  33. package/src/ide-integration/provider-manager.cjs +354 -0
  34. package/src/ide-integration/quota-detector.cjs +34 -0
  35. package/src/ide-integration/quota-detector.js +349 -0
  36. package/src/ide-integration/windows-automation-manager.js +262 -0
  37. package/src/index.cjs +43 -0
  38. package/src/index.js +17 -0
  39. package/src/llm/direct-llm-manager.cjs +609 -0
  40. package/src/ui/ButtonComponents.js +247 -0
  41. package/src/ui/ChatInterface.js +499 -0
  42. package/src/ui/StateManager.js +259 -0
  43. package/src/ui/StateManager.test.js +0 -0
  44. package/src/utils/audit-logger.cjs +116 -0
  45. package/src/utils/config-helpers.cjs +94 -0
  46. package/src/utils/config-helpers.js +94 -0
  47. package/src/utils/electron-update-checker.js +78 -0
  48. package/src/utils/gcloud-auth.cjs +394 -0
  49. package/src/utils/logger.cjs +193 -0
  50. package/src/utils/logger.js +191 -0
  51. package/src/utils/repo-helpers.cjs +120 -0
  52. package/src/utils/repo-helpers.js +120 -0
  53. package/src/utils/requirement-helpers.js +432 -0
  54. package/src/utils/update-checker.js +167 -0
@@ -0,0 +1,1088 @@
1
+ // @vibecodingmachine/core - AppleScript Manager (CommonJS)
2
+ // Handles AppleScript-based interactions with IDEs that don't support CDP
3
+
4
+ const { execSync } = require('child_process');
5
+
6
+ /**
7
+ * AppleScript Manager for IDE interactions
8
+ * Handles AppleScript-based text sending and response reading for IDEs like Cursor and Windsurf
9
+ */
10
+ class AppleScriptManager {
11
+ constructor() {
12
+ this.logger = console;
13
+ }
14
+
15
+ /**
16
+ * Open Cursor IDE
17
+ * @returns {Promise<Object>} Result object with success status and details
18
+ */
19
+ async openCursor() {
20
+ try {
21
+ const appleScript = `
22
+ tell application "Cursor"
23
+ activate
24
+ end tell
25
+ `;
26
+
27
+ execSync(`osascript -e '${appleScript}'`, { encoding: 'utf8' });
28
+
29
+ return {
30
+ success: true,
31
+ message: 'Cursor opened successfully'
32
+ };
33
+ } catch (error) {
34
+ return {
35
+ success: false,
36
+ error: `Failed to open Cursor: ${error.message}`
37
+ };
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Open Windsurf IDE
43
+ * @returns {Promise<Object>} Result object with success status and details
44
+ */
45
+ async openWindsurf() {
46
+ try {
47
+ // Use multiple separate AppleScript calls for better reliability
48
+ try {
49
+ // First, try to launch
50
+ execSync('osascript -e \'tell application "Windsurf" to launch\'', { encoding: 'utf8', timeout: 5000 });
51
+ } catch (launchError) {
52
+ // If launch fails, it might already be running
53
+ }
54
+
55
+ // Wait a moment
56
+ await new Promise(resolve => setTimeout(resolve, 1000));
57
+
58
+ // Then activate
59
+ execSync('osascript -e \'tell application "Windsurf" to activate\'', { encoding: 'utf8', timeout: 5000 });
60
+
61
+ // Wait a moment
62
+ await new Promise(resolve => setTimeout(resolve, 1000));
63
+
64
+ // Finally, ensure it's frontmost
65
+ try {
66
+ execSync('osascript -e \'tell application "System Events" to tell process "Windsurf" to set frontmost to true\'', { encoding: 'utf8', timeout: 5000 });
67
+ } catch (frontmostError) {
68
+ // If setting frontmost fails, that's okay - the app is still activated
69
+ }
70
+
71
+ return {
72
+ success: true,
73
+ message: 'Windsurf launched and activated successfully'
74
+ };
75
+ } catch (error) {
76
+ return {
77
+ success: false,
78
+ error: `Failed to open Windsurf: ${error.message}`
79
+ };
80
+ }
81
+ }
82
+
83
+ async openAntigravity(repoPath = null) {
84
+ try {
85
+ this.logger.log('Opening Google Antigravity...');
86
+
87
+ let command = `open -a "Antigravity"`;
88
+ if (repoPath) {
89
+ command += ` "${repoPath}"`;
90
+ }
91
+
92
+ execSync(command, { encoding: 'utf8', timeout: 5000 });
93
+ await new Promise(resolve => setTimeout(resolve, 2000));
94
+
95
+ return {
96
+ success: true,
97
+ message: repoPath ? `Google Antigravity opened with repository: ${repoPath}` : 'Google Antigravity opened successfully'
98
+ };
99
+ } catch (error) {
100
+ return {
101
+ success: false,
102
+ error: `Failed to open Google Antigravity: ${error.message}`
103
+ };
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Handle Antigravity quota limit by automatically switching models
109
+ * @returns {Promise<{success: boolean, model?: string, error?: string}>}
110
+ */
111
+ async handleAntigravityQuotaLimit() {
112
+ try {
113
+ this.logger.log('Attempting to handle Antigravity quota limit...');
114
+
115
+ const script = `
116
+ tell application "System Events"
117
+ tell process "Antigravity"
118
+ set frontmost to true
119
+ delay 0.8
120
+
121
+ -- Try to find and click "Select another model" button
122
+ try
123
+ set modelButtons to buttons of window 1 whose name contains "Select another model"
124
+ if (count of modelButtons) > 0 then
125
+ click item 1 of modelButtons
126
+ delay 1.5
127
+
128
+ -- Look for model dropdown/menu items
129
+ -- Try alternative models in order of preference
130
+ set modelNames to {"Gemini 3 Pro (Low)", "Claude Sonnet 4.5", "Claude Sonnet 4.5 (Thinking)", "GPT-OSS 120B (Medium)"}
131
+
132
+ repeat with modelName in modelNames
133
+ try
134
+ -- Try to find and click this model option
135
+ set modelItems to menu items of menu 1 of window 1 whose name is modelName
136
+ if (count of modelItems) > 0 then
137
+ click item 1 of modelItems
138
+ delay 0.8
139
+
140
+ -- Click "Accept all" or similar confirmation button
141
+ try
142
+ set acceptButtons to buttons of window 1 whose name contains "Accept"
143
+ if (count of acceptButtons) > 0 then
144
+ click item 1 of acceptButtons
145
+ delay 0.5
146
+ return "success:" & modelName
147
+ end if
148
+ end try
149
+
150
+ return "success:" & modelName
151
+ end if
152
+ end try
153
+ end repeat
154
+
155
+ return "error:no-models-available"
156
+ else
157
+ return "error:button-not-found"
158
+ end if
159
+ on error errMsg
160
+ return "error:" & errMsg
161
+ end try
162
+ end tell
163
+ end tell
164
+ `;
165
+
166
+ const result = execSync(`osascript -e '${script.replace(/'/g, "'\\''")}'`, {
167
+ encoding: 'utf8',
168
+ timeout: 15000
169
+ }).trim();
170
+
171
+ this.logger.log('AppleScript result:', result);
172
+
173
+ if (result.startsWith('success:')) {
174
+ const model = result.substring(8);
175
+ this.logger.log(`Successfully switched to model: ${model}`);
176
+ return { success: true, model };
177
+ } else {
178
+ const error = result.substring(6);
179
+ this.logger.log(`Failed to switch model: ${error}`);
180
+ return { success: false, error };
181
+ }
182
+ } catch (error) {
183
+ this.logger.log('Error handling Antigravity quota limit:', error.message);
184
+ return { success: false, error: error.message };
185
+ }
186
+ }
187
+
188
+ async openIDE(ide, repoPath = null) {
189
+ const ideLower = (ide || '').toLowerCase();
190
+
191
+ switch (ideLower) {
192
+ case 'cursor':
193
+ return await this.openCursor(repoPath);
194
+ case 'windsurf':
195
+ return await this.openWindsurf(repoPath);
196
+ case 'antigravity':
197
+ return await this.openAntigravity(repoPath);
198
+ default:
199
+ return {
200
+ success: false,
201
+ error: `Unknown IDE: ${ide}. Supported: cursor, windsurf, antigravity`
202
+ };
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Send text to an IDE using AppleScript
208
+ * @param {string} text - The text to send
209
+ * @param {string} ide - The IDE name ('cursor', 'windsurf', 'antigravity')
210
+ * @returns {Promise<Object>} Result object with success status and details
211
+ */
212
+ async sendText(text, ide) {
213
+ if (typeof text !== 'string') {
214
+ return {
215
+ success: false,
216
+ error: `Invalid text type: ${typeof text}. Expected string.`,
217
+ debug: { textType: typeof text, textValue: text }
218
+ };
219
+ }
220
+
221
+ const ideName = ide === 'windsurf' ? 'Windsurf' : ide === 'cursor' ? 'Cursor' : ide === 'antigravity' ? 'Antigravity' : 'Unknown IDE';
222
+
223
+ this.logger.log(`${ideName} detected - using AppleScript for reliable text sending`);
224
+
225
+ try {
226
+ let appleScript;
227
+
228
+ if (ide === 'windsurf') {
229
+ // AppleScript for Windsurf - robust chat input targeting
230
+ appleScript = `
231
+ tell application "System Events"
232
+ tell process "Windsurf"
233
+ set frontmost to true
234
+ delay 1
235
+
236
+ -- Method 1: Try to find and click on the chat input field
237
+ try
238
+ set chatInput to text field 1 of group 1 of window 1
239
+ click chatInput
240
+ delay 0.5
241
+ on error
242
+ try
243
+ -- Method 2: Try to find text area for chat
244
+ set chatInput to text area 1 of group 1 of window 1
245
+ click chatInput
246
+ delay 0.5
247
+ on error
248
+ try
249
+ -- Method 3: Try to find any input field
250
+ set chatInput to text field 1 of window 1
251
+ click chatInput
252
+ delay 0.5
253
+ on error
254
+ -- Method 4: Just try typing (fallback)
255
+ delay 0.5
256
+ end try
257
+ end try
258
+ end try
259
+
260
+ -- Type the message
261
+ keystroke "${text.replace(/"/g, '\\"')}"
262
+ delay 0.5
263
+
264
+ -- Try multiple submission methods
265
+ key code 36
266
+ delay 0.5
267
+ key code 36 using {command down}
268
+ delay 0.5
269
+ key code 36 using {command down, shift down}
270
+ delay 0.5
271
+ key code 1 using {command down}
272
+ delay 0.5
273
+ end tell
274
+ end tell
275
+ `;
276
+ } else if (ide === 'cursor') {
277
+ // AppleScript for Cursor - proven to work (from commit 3403c17)
278
+ appleScript = `
279
+ tell application "System Events"
280
+ tell process "Cursor"
281
+ set frontmost to true
282
+ delay 1
283
+
284
+ -- DYNAMIC SCREENSHOT-BASED CHAT INPUT TARGETING
285
+ -- Method 1: Take screenshot of the monitor where Cursor is running
286
+ try
287
+ -- Get the bounds of the Cursor window to determine which monitor it's on
288
+ set cursorBounds to bounds of window 1
289
+ set cursorX to item 1 of cursorBounds
290
+ set cursorY to item 2 of cursorBounds
291
+
292
+ -- Take a screenshot of the specific monitor where Cursor is located
293
+ set screenshotPath to (path to desktop as string) & "cursor_screenshot.png"
294
+
295
+ -- Use screencapture with display selection based on Cursor window position
296
+ -- If Cursor is on the right side (aux monitor), use display 2
297
+ if cursorX > 1000 then
298
+ do shell script "screencapture -D 2 -x " & quoted form of POSIX path of screenshotPath
299
+ else
300
+ do shell script "screencapture -D 1 -x " & quoted form of POSIX path of screenshotPath
301
+ end if
302
+ delay 0.5
303
+
304
+ -- Use Python with PIL to analyze the screenshot and find text input area
305
+ set pythonScript to "
306
+ import sys
307
+ import os
308
+ from PIL import Image, ImageDraw
309
+ import subprocess
310
+
311
+ try:
312
+ # Read the screenshot
313
+ img = Image.open('" & POSIX path of screenshotPath & "')
314
+ width, height = img.size
315
+
316
+ # Convert to grayscale for analysis
317
+ gray = img.convert('L')
318
+
319
+ # Get screen dimensions to calculate relative positions
320
+ screen_width = width
321
+ screen_height = height
322
+
323
+ # Determine if this is the auxiliary monitor based on Cursor window position
324
+ cursor_x = " & cursorX & "
325
+ is_aux_monitor = cursor_x > 1000
326
+
327
+ if is_aux_monitor:
328
+ # For auxiliary monitor, adjust coordinates
329
+ # Chat input is typically in the right side of the aux monitor
330
+ candidates = [
331
+ (int(screen_width * 0.8), int(screen_height * 0.85)), # Bottom-right
332
+ (int(screen_width * 0.75), int(screen_height * 0.8)), # Slightly up
333
+ (int(screen_width * 0.85), int(screen_height * 0.8)), # More right
334
+ (int(screen_width * 0.7), int(screen_height * 0.9)), # Bottom-left of right area
335
+ ]
336
+ else:
337
+ # For main monitor, use standard coordinates
338
+ candidates = [
339
+ (int(screen_width * 0.8), int(screen_height * 0.85)), # Bottom-right
340
+ (int(screen_width * 0.75), int(screen_height * 0.8)), # Slightly up
341
+ (int(screen_width * 0.85), int(screen_height * 0.8)), # More right
342
+ (int(screen_width * 0.7), int(screen_height * 0.9)), # Bottom-left of right area
343
+ ]
344
+
345
+ # Use the first candidate (bottom-right area)
346
+ x, y = candidates[0]
347
+ print(f'{x},{y}')
348
+
349
+ except Exception as e:
350
+ # Fallback to estimated coordinates based on monitor
351
+ if " & cursorX & " > 1000:
352
+ print('1000,600') # Aux monitor fallback
353
+ else:
354
+ print('800,500') # Main monitor fallback
355
+ "
356
+
357
+ -- Execute Python script to find coordinates
358
+ set coordinates to do shell script "python3 -c " & quoted form of pythonScript
359
+ delay 0.3
360
+
361
+ -- Click at the detected coordinates
362
+ do shell script "/usr/local/bin/cliclick c:" & coordinates
363
+ delay 0.5
364
+
365
+ -- Clean up screenshot
366
+ do shell script "rm " & quoted form of POSIX path of screenshotPath
367
+
368
+ on error
369
+ -- Fallback: Use estimated coordinates if screenshot analysis fails
370
+ try
371
+ -- Click in the main editor area to escape terminal
372
+ do shell script "/usr/local/bin/cliclick c:600,400"
373
+ delay 0.3
374
+
375
+ -- Click in the chat panel area to focus it
376
+ do shell script "/usr/local/bin/cliclick c:1200,500"
377
+ delay 0.3
378
+
379
+ -- Click in the chat input area specifically
380
+ do shell script "/usr/local/bin/cliclick c:1100,700"
381
+ delay 0.3
382
+
383
+ on error
384
+ delay 0.3
385
+ end try
386
+ end try
387
+
388
+ -- Open new chat session with Cmd+T to prevent crashes
389
+ key code 17 using {command down} -- Cmd+T
390
+ delay 1.0
391
+
392
+ -- Type the message using clipboard (more reliable than keystroke)
393
+ set the clipboard to "${text.replace(/"/g, '\\"')}"
394
+ delay 0.5
395
+ key code 9 using {command down} -- Cmd+V to paste
396
+ delay 0.5
397
+
398
+ -- Press Cmd+Enter to send (more reliable than just Enter)
399
+ key code 36 using {command down}
400
+ delay 1
401
+
402
+ return "Message sent via AppleScript"
403
+ end tell
404
+ end tell
405
+ `;
406
+ } else if (ide === 'antigravity') {
407
+ // AppleScript for Google Antigravity - Cmd+Shift+L opens Agent chat (non-toggle)
408
+ appleScript = `
409
+ tell application "System Events"
410
+ tell process "Antigravity"
411
+ set frontmost to true
412
+ delay 1
413
+
414
+ -- Open Agent chat with Cmd+Shift+L (does not toggle)
415
+ key code 37 using {command down, shift down}
416
+ delay 1.5
417
+
418
+ -- Type the message
419
+ keystroke "${text.replace(/"/g, '\\"')}"
420
+ delay 0.5
421
+
422
+ -- Send with Enter
423
+ key code 36
424
+ delay 0.5
425
+ end tell
426
+ end tell
427
+ `;
428
+ } else {
429
+ return {
430
+ success: false,
431
+ error: `Unsupported IDE for AppleScript: ${ide}`,
432
+ note: 'AppleScript is only supported for Cursor, Windsurf, and Antigravity'
433
+ };
434
+ }
435
+
436
+ execSync(`osascript -e '${appleScript}'`, { stdio: 'pipe' });
437
+
438
+ this.logger.log(`Successfully sent message to ${ideName} via AppleScript`);
439
+ return {
440
+ success: true,
441
+ method: 'applescript',
442
+ message: `Message sent to ${ideName}: ${text}`,
443
+ note: 'Message sent via AppleScript automation'
444
+ };
445
+
446
+ } catch (error) {
447
+ this.logger.log('AppleScript interaction failed, falling back to simulated response:', error.message);
448
+
449
+ // Fallback to simulated response if AppleScript fails
450
+ return {
451
+ success: true,
452
+ method: 'simulated',
453
+ message: `Simulated ${ideName} response: ${text}`,
454
+ note: `${ideName} AppleScript automation failed. Using simulated response for testing.`
455
+ };
456
+ }
457
+ }
458
+
459
+ /**
460
+ * Read chat response from an IDE using AppleScript
461
+ * @param {string} ide - The IDE name ('cursor' or 'windsurf')
462
+ * @returns {Promise<string>} The chat response text
463
+ */
464
+ async readChatResponse(ide) {
465
+ if (ide !== 'windsurf' && ide !== 'cursor') {
466
+ return 'Error: AppleScript reading is only supported for Cursor and Windsurf';
467
+ }
468
+
469
+ this.logger.log(`${ide === 'windsurf' ? 'Windsurf' : 'Cursor'} detected - using AppleScript to read chat response`);
470
+
471
+ try {
472
+ // For Cursor, try CDP first if available
473
+ if (ide === 'cursor') {
474
+ try {
475
+ this.logger.log('🔧 Attempting CDP method for Cursor...');
476
+ const cdpResponse = await this.readCursorResponseViaCDP();
477
+ if (cdpResponse && cdpResponse.length > 20) {
478
+ this.logger.log('✅ Successfully read Cursor response via CDP');
479
+ return cdpResponse;
480
+ }
481
+ } catch (error) {
482
+ this.logger.log('⚠️ CDP method failed, falling back to AppleScript:', error.message);
483
+ }
484
+ }
485
+
486
+ // Try clipboard-based response detection
487
+ if (ide === 'cursor') {
488
+ try {
489
+ this.logger.log('🔧 Attempting clipboard-based response detection...');
490
+ const clipboardResponse = await this.readCursorResponseViaClipboard();
491
+ if (clipboardResponse && clipboardResponse.length > 20) {
492
+ this.logger.log('✅ Successfully read Cursor response via clipboard');
493
+ return clipboardResponse;
494
+ }
495
+ } catch (error) {
496
+ this.logger.log('⚠️ Clipboard method failed, falling back to AppleScript:', error.message);
497
+ }
498
+ }
499
+
500
+ } catch (error) {
501
+ this.logger.log('⚠️ Fallback methods failed:', error.message);
502
+ }
503
+
504
+ try {
505
+ // AppleScript to read the chat response from IDE
506
+ const appleScript = `
507
+ tell application "System Events"
508
+ tell process "${ide === 'windsurf' ? 'Windsurf' : 'Cursor'}"
509
+ set frontmost to true
510
+ delay 1
511
+
512
+ ${ide === 'cursor' ? `
513
+ -- Cursor-specific methods
514
+ -- Method 1: Try to get text from web view content (most likely location)
515
+ try
516
+ set webViewGroups to group of window 1
517
+ set responseText to ""
518
+ repeat with grp in webViewGroups
519
+ try
520
+ -- Look for text areas within groups (web view content)
521
+ set textAreas to text area of grp
522
+ repeat with txtArea in textAreas
523
+ try
524
+ set textValue to value of txtArea
525
+ if textValue is not "" and textValue is not missing value then
526
+ set responseText to responseText & textValue & "\\n"
527
+ end if
528
+ on error
529
+ -- Continue to next text area
530
+ end try
531
+ end repeat
532
+
533
+ -- Look for static text within groups
534
+ set staticTexts to static text of grp
535
+ repeat with txt in staticTexts
536
+ try
537
+ set textValue to value of txt
538
+ if textValue is not "" and textValue is not missing value then
539
+ set responseText to responseText & textValue & "\\n"
540
+ end if
541
+ on error
542
+ -- Continue to next static text
543
+ end try
544
+ end repeat
545
+ on error
546
+ -- Continue to next group
547
+ end try
548
+ end repeat
549
+ if responseText is not "" then
550
+ return responseText
551
+ end if
552
+ on error
553
+ -- Continue to next method
554
+ end try
555
+
556
+ -- Method 2: Try to get all static text from the window
557
+ try
558
+ set allText to value of static text of window 1
559
+ if allText is not "" then
560
+ return allText
561
+ end if
562
+ on error
563
+ -- Continue to next method
564
+ end try
565
+
566
+ -- Method 3: Try to get text from scroll areas
567
+ try
568
+ set scrollAreas to scroll area of window 1
569
+ set responseText to ""
570
+ repeat with scrollArea in scrollAreas
571
+ try
572
+ set scrollText to value of static text of scrollArea
573
+ if scrollText is not "" then
574
+ set responseText to responseText & scrollText & "\\n"
575
+ end if
576
+ on error
577
+ -- Continue to next scroll area
578
+ end try
579
+ end repeat
580
+ if responseText is not "" then
581
+ return responseText
582
+ end if
583
+ on error
584
+ -- Continue to next method
585
+ end try
586
+
587
+ -- Method 4: Try accessibility API approach
588
+ try
589
+ set uiElements to every UI element of window 1
590
+ set responseText to ""
591
+ repeat with element in uiElements
592
+ try
593
+ set elementText to value of element
594
+ if elementText is not "" and elementText is not missing value then
595
+ set responseText to responseText & elementText & "\\n"
596
+ end if
597
+ on error
598
+ -- Continue to next element
599
+ end try
600
+ end repeat
601
+ if responseText is not "" then
602
+ return responseText
603
+ end if
604
+ on error
605
+ -- Continue to next method
606
+ end try
607
+
608
+ -- Method 5: Return diagnostic information
609
+ return "DEBUG: Window count: " & count of windows & "\\nText areas found: " & count of text area of window 1 & "\\nStatic text found: " & value of static text of window 1 & "\\n\\n\\nRESPONSE: "
610
+ ` : `
611
+ -- Windsurf-specific methods
612
+ -- Method 1: Try to get all static text from the window
613
+ try
614
+ set allText to value of static text of window 1
615
+ if allText is not "" then
616
+ return allText
617
+ end if
618
+ on error
619
+ -- Continue to next method
620
+ end try
621
+
622
+ -- Method 2: Try to get text from groups
623
+ try
624
+ set groups to group of window 1
625
+ set responseText to ""
626
+ repeat with grp in groups
627
+ try
628
+ set groupText to value of static text of grp
629
+ if groupText is not "" then
630
+ set responseText to responseText & groupText & "\\n"
631
+ end if
632
+ on error
633
+ -- Continue to next group
634
+ end try
635
+ end repeat
636
+ if responseText is not "" then
637
+ return responseText
638
+ end if
639
+ on error
640
+ -- Continue to next method
641
+ end try
642
+
643
+ -- Method 3: Try clipboard approach
644
+ try
645
+ key code 0 using {command down}
646
+ delay 0.5
647
+ key code 8 using {command down}
648
+ delay 0.5
649
+ set clipboardText to (the clipboard)
650
+ if clipboardText is not "" then
651
+ return clipboardText
652
+ end if
653
+ on error
654
+ -- Continue to next method
655
+ end try
656
+
657
+ -- Method 4: Return placeholder
658
+ return "No chat content found in Windsurf"
659
+ `}
660
+ end tell
661
+ end tell
662
+ `;
663
+
664
+ const result = execSync(`osascript -e '${appleScript}'`, { stdio: 'pipe', encoding: 'utf8' });
665
+ this.logger.log('Successfully read response via AppleScript');
666
+ this.logger.log('📖 AppleScript result:', result);
667
+ this.logger.log('📏 Result length:', result.length);
668
+ this.logger.log('🔍 Result preview:', result.substring(0, 200));
669
+
670
+ // Check if the result is just diagnostic information
671
+ const isDiagnosticResult = result.includes('DEBUG:') ||
672
+ result.includes('diagnostic') ||
673
+ result.includes('No chat content found') ||
674
+ result.includes('Text areas found: 0') ||
675
+ result.includes('Static text found:') ||
676
+ result.includes('Window count:') ||
677
+ result.includes('Sample text:') ||
678
+ result.includes('Could not read') ||
679
+ result.includes('No readable text content detected');
680
+
681
+ this.logger.log(`🔍 Diagnostic detection check: isDiagnosticResult = ${isDiagnosticResult}`);
682
+ this.logger.log(`🔍 Result contains 'DEBUG:': ${result.includes('DEBUG:')}`);
683
+ this.logger.log(`🔍 Result contains 'Window count:': ${result.includes('Window count:')}`);
684
+
685
+ // For Cursor, always try CDP and clipboard methods as fallbacks
686
+ if (ide === 'cursor') {
687
+ this.logger.log('🔧 For Cursor, always trying CDP and clipboard methods as fallbacks...');
688
+
689
+ // Try CDP method
690
+ try {
691
+ this.logger.log('🔧 Attempting CDP method for Cursor...');
692
+ const cdpResponse = await this.readCursorResponseViaCDP();
693
+ if (cdpResponse && cdpResponse.length > 20) {
694
+ this.logger.log('✅ Successfully read Cursor response via CDP');
695
+ return cdpResponse;
696
+ }
697
+ } catch (error) {
698
+ this.logger.log('⚠️ CDP method failed:', error.message);
699
+ }
700
+
701
+ // Try clipboard method
702
+ try {
703
+ this.logger.log('🔧 Attempting clipboard-based response detection...');
704
+ const clipboardResponse = await this.readCursorResponseViaClipboard();
705
+ if (clipboardResponse && clipboardResponse.length > 20) {
706
+ this.logger.log('✅ Successfully read Cursor response via clipboard');
707
+ return clipboardResponse;
708
+ }
709
+ } catch (error) {
710
+ this.logger.log('⚠️ Clipboard method failed:', error.message);
711
+ }
712
+ }
713
+
714
+ if (isDiagnosticResult) {
715
+ this.logger.log('⚠️ AppleScript returned diagnostic info, all methods failed...');
716
+ return `Unable to read ${ide} response - all methods failed. AppleScript diagnostic: ${result.substring(0, 200)}...`;
717
+ }
718
+
719
+ return result.trim();
720
+
721
+ } catch (error) {
722
+ this.logger.log('AppleScript reading failed:', error.message);
723
+ return 'Error: Could not read Windsurf response via AppleScript';
724
+ }
725
+ }
726
+
727
+ /**
728
+ * Detect quota warnings using AppleScript
729
+ * @param {string} ide - The IDE name ('windsurf')
730
+ * @returns {Promise<Object>} Quota detection result
731
+ */
732
+ async detectQuotaWarning(ide) {
733
+ if (ide !== 'windsurf') {
734
+ return {
735
+ hasQuotaWarning: false,
736
+ note: 'AppleScript quota detection is only supported for Windsurf'
737
+ };
738
+ }
739
+
740
+ this.logger.log('🔍 === APPLESCRIPT DETECTION START ===');
741
+ this.logger.log('🔍 Starting enhanced AppleScript quota detection...');
742
+
743
+ try {
744
+ // Test basic AppleScript execution first
745
+ this.logger.log('🔍 Testing basic AppleScript execution...');
746
+ const basicTest = execSync('osascript -e "return \\"test\\""', { stdio: 'pipe', encoding: 'utf8' });
747
+ this.logger.log('✅ Basic AppleScript test passed:', basicTest.trim());
748
+ } catch (basicError) {
749
+ this.logger.log('❌ Basic AppleScript test failed:', basicError.message);
750
+ return {
751
+ hasQuotaWarning: false,
752
+ error: 'AppleScript not available',
753
+ note: 'Basic AppleScript test failed'
754
+ };
755
+ }
756
+
757
+ // Try a much simpler AppleScript first
758
+ this.logger.log('🔍 Trying simple AppleScript approach...');
759
+ const simpleAppleScript = `
760
+ tell application "System Events"
761
+ tell process "Windsurf"
762
+ set frontmost to true
763
+ delay 1
764
+
765
+ -- Method 1: Look for static text containing quota warnings
766
+ try
767
+ set allText to value of static text of window 1
768
+ if allText contains "Not enough credits" or allText contains "Upgrade to a paid plan" or allText contains "switch to SWE-1-lite" then
769
+ return "QUOTA_WARNING_DETECTED: " & allText
770
+ end if
771
+ on error
772
+ -- Continue to next method
773
+ end try
774
+
775
+ -- Method 2: Look for disabled text areas (quota warnings often disable chat input)
776
+ try
777
+ set textAreas to text area of window 1
778
+ repeat with textArea in textAreas
779
+ try
780
+ if not enabled of textArea then
781
+ return "QUOTA_INDICATOR_DETECTED: Disabled text area found"
782
+ end if
783
+ on error
784
+ -- Continue to next text area
785
+ end try
786
+ end repeat
787
+ on error
788
+ -- Continue to next method
789
+ end try
790
+
791
+ -- Method 3: Look for specific UI elements that indicate quota issues
792
+ try
793
+ set buttons to button of window 1
794
+ repeat with btn in buttons
795
+ try
796
+ set buttonText to title of btn
797
+ if buttonText contains "Upgrade" or buttonText contains "credits" or buttonText contains "quota" then
798
+ return "QUOTA_UI_DETECTED: " & buttonText
799
+ end if
800
+ on error
801
+ -- Continue to next button
802
+ end try
803
+ end repeat
804
+ on error
805
+ -- Continue to next method
806
+ end try
807
+
808
+ -- Method 4: Look for groups that might contain quota information
809
+ try
810
+ set groups to group of window 1
811
+ repeat with grp in groups
812
+ try
813
+ set groupText to value of static text of grp
814
+ if groupText contains "credits" or groupText contains "quota" or groupText contains "upgrade" then
815
+ return "QUOTA_GROUP_DETECTED: " & groupText
816
+ end if
817
+ on error
818
+ -- Continue to next group
819
+ end try
820
+ end repeat
821
+ on error
822
+ -- Continue to next method
823
+ end try
824
+
825
+ return "NO_QUOTA_WARNING_DETECTED"
826
+ end tell
827
+ end tell
828
+ `;
829
+
830
+ this.logger.log('🔍 Simple AppleScript length:', simpleAppleScript.length);
831
+ this.logger.log('🔍 Simple AppleScript preview:', simpleAppleScript.substring(0, 200) + '...');
832
+
833
+ try {
834
+ this.logger.log('🔍 Executing simple AppleScript...');
835
+ this.logger.log('🔍 AppleScript command length:', simpleAppleScript.length);
836
+
837
+ const result = execSync(`osascript -e '${simpleAppleScript}'`, { stdio: 'pipe', encoding: 'utf8' });
838
+ this.logger.log('🔍 AppleScript execution completed');
839
+ this.logger.log('🔍 AppleScript result length:', result.length);
840
+ this.logger.log('🔍 AppleScript result:', result.trim());
841
+ this.logger.log('🔍 AppleScript result starts with:', result.trim().substring(0, 100));
842
+
843
+ // Check for quota warnings in the result
844
+ if (result.includes('QUOTA_WARNING_DETECTED')) {
845
+ this.logger.log('❌ Windsurf quota warning detected via enhanced AppleScript');
846
+ return {
847
+ hasQuotaWarning: true,
848
+ method: 'applescript-static-text',
849
+ note: 'Windsurf quota warning detected via enhanced AppleScript detection'
850
+ };
851
+ }
852
+
853
+ if (result.includes('QUOTA_INDICATOR_DETECTED')) {
854
+ this.logger.log('❌ Windsurf quota indicator detected via enhanced AppleScript');
855
+ return {
856
+ hasQuotaWarning: true,
857
+ method: 'applescript-disabled-input',
858
+ note: 'Windsurf quota indicator detected via enhanced AppleScript (disabled chat input)'
859
+ };
860
+ }
861
+
862
+ if (result.includes('QUOTA_UI_DETECTED') || result.includes('QUOTA_GROUP_DETECTED')) {
863
+ this.logger.log('⚠️ Windsurf potential quota text detected via enhanced AppleScript');
864
+ return {
865
+ hasQuotaWarning: true,
866
+ method: 'applescript-ui-elements',
867
+ note: 'Windsurf potential quota text detected via enhanced AppleScript'
868
+ };
869
+ }
870
+
871
+ // Additional pattern matching for quota-related text
872
+ const quotaPatterns = [
873
+ 'not enough credits',
874
+ 'upgrade to a paid plan',
875
+ 'switch to swe-1-lite',
876
+ 'quota exceeded',
877
+ 'credits exhausted',
878
+ 'payment required'
879
+ ];
880
+
881
+ const lowerResult = result.toLowerCase();
882
+ for (const pattern of quotaPatterns) {
883
+ if (lowerResult.includes(pattern)) {
884
+ this.logger.log('❌ Windsurf quota warning detected via text pattern matching');
885
+ return {
886
+ hasQuotaWarning: true,
887
+ method: 'applescript-pattern-matching',
888
+ pattern: pattern,
889
+ note: 'Windsurf quota warning detected via text pattern matching in AppleScript result'
890
+ };
891
+ }
892
+ }
893
+
894
+ this.logger.log('🔍 === APPLESCRIPT DETECTION END ===');
895
+ this.logger.log('🔍 Since AppleScript cannot access web view, trying alternative methods...');
896
+
897
+ // Since AppleScript cannot access web view content, return a note about the limitation
898
+ return {
899
+ hasQuotaWarning: false,
900
+ method: 'applescript-limited',
901
+ note: 'Windsurf quota warning is visible in UI but not detectable via AppleScript'
902
+ };
903
+
904
+ } catch (error) {
905
+ this.logger.log('❌ AppleScript error:', error.message);
906
+ return {
907
+ hasQuotaWarning: false,
908
+ error: error.message,
909
+ note: 'AppleScript cannot access web view content - Windsurf does not support CDP'
910
+ };
911
+ }
912
+ }
913
+
914
+ /**
915
+ * Read Cursor response via Chrome DevTools Protocol (CDP)
916
+ * @returns {Promise<string>} The chat response text
917
+ */
918
+ async readCursorResponseViaCDP() {
919
+ try {
920
+ this.logger.log('🔧 Attempting CDP connection to Cursor on port 9225...');
921
+
922
+ // Import CDP dynamically to avoid issues in VSCode extension context
923
+ const CDP = require('chrome-remote-interface');
924
+
925
+ // List available targets
926
+ const targets = await CDP.List({ port: 9225 });
927
+ this.logger.log(`🔧 Found ${targets.length} CDP targets`);
928
+
929
+ // Find the main workbench target (not settings)
930
+ const workbench = targets.find(t =>
931
+ t.title !== 'Cursor Settings' &&
932
+ t.type === 'page' &&
933
+ t.url && t.url.includes('workbench')
934
+ ) || targets[0];
935
+
936
+ if (!workbench) {
937
+ throw new Error('No suitable Cursor workbench target found');
938
+ }
939
+
940
+ this.logger.log(`🔧 Connecting to target: ${workbench.title}`);
941
+
942
+ // Connect to the target
943
+ const client = await CDP({ port: 9225, target: workbench });
944
+ const { Runtime, DOM } = client;
945
+
946
+ // Enable required domains
947
+ await Runtime.enable();
948
+ await DOM.enable();
949
+
950
+ this.logger.log('🔧 CDP connection established, searching for chat content...');
951
+
952
+ // Try multiple selectors to find chat content
953
+ const selectors = [
954
+ '.chat-content',
955
+ '.chat-messages',
956
+ '.response-content',
957
+ '.ai-response',
958
+ '.message-content',
959
+ '[data-testid="chat-message"]',
960
+ '.markdown-content',
961
+ '.prose',
962
+ '.chat-panel',
963
+ '.sidebar-panel'
964
+ ];
965
+
966
+ for (const selector of selectors) {
967
+ try {
968
+ this.logger.log(`🔧 Trying selector: ${selector}`);
969
+
970
+ // Get document root
971
+ const { root } = await DOM.getDocument();
972
+
973
+ // Search for elements with the selector
974
+ const { nodeIds } = await DOM.querySelectorAll({ nodeId: root.nodeId, selector });
975
+
976
+ if (nodeIds.length > 0) {
977
+ this.logger.log(`🔧 Found ${nodeIds.length} elements with selector: ${selector}`);
978
+
979
+ // Get text content from the first matching element
980
+ const { nodeId } = await DOM.describeNode({ nodeId: nodeIds[0] });
981
+ const { outerHTML } = await DOM.getOuterHTML({ nodeId });
982
+
983
+ // Extract text content (remove HTML tags)
984
+ const textContent = outerHTML.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
985
+
986
+ if (textContent && textContent.length > 20) {
987
+ this.logger.log(`✅ Successfully extracted chat content via CDP: ${textContent.substring(0, 100)}...`);
988
+ await client.close();
989
+ return textContent;
990
+ }
991
+ }
992
+ } catch (error) {
993
+ this.logger.log(`⚠️ Selector ${selector} failed: ${error.message}`);
994
+ }
995
+ }
996
+
997
+ // If no specific selectors work, try to get all text content
998
+ this.logger.log('🔧 Trying to get all text content from the page...');
999
+
1000
+ const { root } = await DOM.getDocument();
1001
+ const { outerHTML } = await DOM.getOuterHTML({ nodeId: root.nodeId });
1002
+
1003
+ // Extract text content and look for AI responses
1004
+ const textContent = outerHTML.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
1005
+
1006
+ // Look for patterns that indicate AI responses
1007
+ const aiResponsePatterns = [
1008
+ /(?:AI|Assistant|Claude|GPT|Response):\s*([^]*?)(?=\n\n|\n[A-Z]|$)/gi,
1009
+ /(?:Here|This|The answer|The solution):\s*([^]*?)(?=\n\n|\n[A-Z]|$)/gi
1010
+ ];
1011
+
1012
+ for (const pattern of aiResponsePatterns) {
1013
+ const matches = textContent.match(pattern);
1014
+ if (matches && matches.length > 0) {
1015
+ const response = matches[matches.length - 1]; // Get the latest response
1016
+ if (response && response.length > 20) {
1017
+ this.logger.log(`✅ Found AI response via CDP pattern matching: ${response.substring(0, 100)}...`);
1018
+ await client.close();
1019
+ return response;
1020
+ }
1021
+ }
1022
+ }
1023
+
1024
+ await client.close();
1025
+ throw new Error('No chat content found via CDP');
1026
+
1027
+ } catch (error) {
1028
+ this.logger.log(`❌ CDP method failed: ${error.message}`);
1029
+ throw error;
1030
+ }
1031
+ }
1032
+
1033
+ /**
1034
+ * Read Cursor response via clipboard (select all and copy)
1035
+ * @returns {Promise<string>} The chat response text
1036
+ */
1037
+ async readCursorResponseViaClipboard() {
1038
+ try {
1039
+ this.logger.log('🔧 Attempting clipboard-based response detection...');
1040
+
1041
+ // AppleScript to select all content in Cursor and copy to clipboard
1042
+ const clipboardScript = `
1043
+ tell application "System Events"
1044
+ tell process "Cursor"
1045
+ set frontmost to true
1046
+ delay 1
1047
+
1048
+ -- Try to focus on the chat area first
1049
+ try
1050
+ key code 1 using {command down} -- Cmd+A to select all
1051
+ delay 0.5
1052
+ key code 8 using {command down} -- Cmd+C to copy
1053
+ delay 0.5
1054
+ on error
1055
+ -- If that fails, try alternative approach
1056
+ key code 1 using {command down, shift down} -- Cmd+Shift+A to select all
1057
+ delay 0.5
1058
+ key code 8 using {command down} -- Cmd+C to copy
1059
+ delay 0.5
1060
+ end try
1061
+ end tell
1062
+ end tell
1063
+ `;
1064
+
1065
+ execSync(`osascript -e '${clipboardScript}'`, { stdio: 'pipe' });
1066
+
1067
+ // Read from clipboard
1068
+ const clipboardScript2 = `
1069
+ the clipboard
1070
+ `;
1071
+
1072
+ const clipboardContent = execSync(`osascript -e '${clipboardScript2}'`, { stdio: 'pipe', encoding: 'utf8' });
1073
+
1074
+ if (clipboardContent && clipboardContent.trim().length > 20) {
1075
+ this.logger.log(`✅ Successfully read clipboard content: ${clipboardContent.substring(0, 100)}...`);
1076
+ return clipboardContent.trim();
1077
+ }
1078
+
1079
+ throw new Error('Clipboard content is empty or too short');
1080
+
1081
+ } catch (error) {
1082
+ this.logger.log(`❌ Clipboard method failed: ${error.message}`);
1083
+ throw error;
1084
+ }
1085
+ }
1086
+ }
1087
+
1088
+ module.exports = { AppleScriptManager };