vibecodingmachine-core 2026.2.20-438 → 2026.2.26-1739

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 (202) hide show
  1. package/README.md +240 -0
  2. package/package.json +10 -2
  3. package/src/agents/Agent.js +300 -0
  4. package/src/agents/AgentAdditionService.js +311 -0
  5. package/src/agents/AgentCheckService.js +690 -0
  6. package/src/agents/AgentInstallationService.js +140 -0
  7. package/src/agents/AgentSetupService.js +467 -0
  8. package/src/agents/AgentStatus.js +183 -0
  9. package/src/agents/AgentVerificationService.js +634 -0
  10. package/src/agents/ConfigurationSchemaValidator.js +543 -0
  11. package/src/agents/EnvironmentConfigurationManager.js +602 -0
  12. package/src/agents/InstallationErrorHandler.js +372 -0
  13. package/src/agents/InstallationLog.js +363 -0
  14. package/src/agents/InstallationMethod.js +510 -0
  15. package/src/agents/InstallationOrchestrator.js +352 -0
  16. package/src/agents/InstallationProgressReporter.js +372 -0
  17. package/src/agents/InstallationRetryManager.js +322 -0
  18. package/src/agents/InstallationType.js +254 -0
  19. package/src/agents/OperationTypes.js +310 -0
  20. package/src/agents/PerformanceMetricsCollector.js +493 -0
  21. package/src/agents/SecurityValidationService.js +534 -0
  22. package/src/agents/VerificationTest.js +354 -0
  23. package/src/agents/VerificationType.js +226 -0
  24. package/src/agents/WindowsPermissionHandler.js +518 -0
  25. package/src/agents/config/AgentConfigManager.js +393 -0
  26. package/src/agents/config/AgentDefaultsRegistry.js +373 -0
  27. package/src/agents/config/ConfigValidator.js +281 -0
  28. package/src/agents/discovery/AgentDiscoveryService.js +707 -0
  29. package/src/agents/logging/AgentLogger.js +511 -0
  30. package/src/agents/status/AgentStatusManager.js +481 -0
  31. package/src/agents/storage/FileManager.js +454 -0
  32. package/src/agents/verification/AgentCommunicationTester.js +474 -0
  33. package/src/agents/verification/BaseVerifier.js +430 -0
  34. package/src/agents/verification/CommandVerifier.js +480 -0
  35. package/src/agents/verification/FileOperationVerifier.js +453 -0
  36. package/src/agents/verification/ResultAnalyzer.js +707 -0
  37. package/src/agents/verification/TestRequirementManager.js +495 -0
  38. package/src/agents/verification/VerificationRunner.js +433 -0
  39. package/src/agents/windows/BaseWindowsInstaller.js +441 -0
  40. package/src/agents/windows/ChocolateyInstaller.js +509 -0
  41. package/src/agents/windows/DirectInstaller.js +443 -0
  42. package/src/agents/windows/InstallerFactory.js +391 -0
  43. package/src/agents/windows/NpmInstaller.js +505 -0
  44. package/src/agents/windows/PowerShellInstaller.js +458 -0
  45. package/src/agents/windows/WinGetInstaller.js +390 -0
  46. package/src/analysis/analysis-reporter.js +132 -0
  47. package/src/analysis/boundary-detector.js +712 -0
  48. package/src/analysis/categorizer.js +340 -0
  49. package/src/analysis/codebase-scanner.js +384 -0
  50. package/src/analysis/line-counter.js +513 -0
  51. package/src/analysis/priority-calculator.js +679 -0
  52. package/src/analysis/report/analysis-report.js +250 -0
  53. package/src/analysis/report/package-analyzer.js +278 -0
  54. package/src/analysis/report/recommendation-generator.js +382 -0
  55. package/src/analysis/report/statistics-generator.js +515 -0
  56. package/src/analysis/reports/analysis-report-model.js +101 -0
  57. package/src/analysis/reports/recommendation-generator.js +283 -0
  58. package/src/analysis/reports/report-generators.js +191 -0
  59. package/src/analysis/reports/statistics-calculator.js +231 -0
  60. package/src/analysis/reports/trend-analyzer.js +219 -0
  61. package/src/analysis/strategy-generator.js +814 -0
  62. package/src/auto-mode/AutoModeBusinessLogic.js +836 -0
  63. package/src/config/refactoring-config.js +307 -0
  64. package/src/health-tracking/json-storage.js +38 -2
  65. package/src/ide-integration/applescript-manager-core.js +233 -0
  66. package/src/ide-integration/applescript-manager.cjs +357 -28
  67. package/src/ide-integration/applescript-manager.js +89 -3599
  68. package/src/ide-integration/cdp-manager.js +306 -0
  69. package/src/ide-integration/claude-code-cli-manager.cjs +1 -1
  70. package/src/ide-integration/continuation-handler.js +337 -0
  71. package/src/ide-integration/ide-status-checker.js +292 -0
  72. package/src/ide-integration/macos-ide-manager.js +627 -0
  73. package/src/ide-integration/macos-text-sender.js +528 -0
  74. package/src/ide-integration/response-reader.js +548 -0
  75. package/src/ide-integration/windows-automation-manager.js +121 -0
  76. package/src/ide-integration/windows-ide-manager.js +373 -0
  77. package/src/index.cjs +25 -3
  78. package/src/index.js +15 -1
  79. package/src/llm/direct-llm-manager.cjs +90 -2
  80. package/src/models/compliance-report.js +538 -0
  81. package/src/models/file-analysis.js +681 -0
  82. package/src/models/refactoring-plan.js +770 -0
  83. package/src/monitoring/alert-system.js +834 -0
  84. package/src/monitoring/compliance-progress-tracker.js +437 -0
  85. package/src/monitoring/continuous-scan-notifications.js +661 -0
  86. package/src/monitoring/continuous-scanner.js +279 -0
  87. package/src/monitoring/file-monitor/file-analyzer.js +262 -0
  88. package/src/monitoring/file-monitor/file-monitor.js +237 -0
  89. package/src/monitoring/file-monitor/watcher.js +194 -0
  90. package/src/monitoring/file-monitor.js +17 -0
  91. package/src/monitoring/notification-manager.js +437 -0
  92. package/src/monitoring/scanner-core.js +368 -0
  93. package/src/monitoring/scanner-events.js +214 -0
  94. package/src/monitoring/violation-notification-system.js +515 -0
  95. package/src/refactoring/boundaries/cohesion-analyzer.js +316 -0
  96. package/src/refactoring/boundaries/extraction-result.js +285 -0
  97. package/src/refactoring/boundaries/extraction-strategies.js +392 -0
  98. package/src/refactoring/boundaries/module-boundary.js +209 -0
  99. package/src/refactoring/boundary/boundary-detector.js +741 -0
  100. package/src/refactoring/boundary/boundary-types.js +405 -0
  101. package/src/refactoring/boundary/extraction-strategies.js +554 -0
  102. package/src/refactoring/boundary-extraction-result.js +77 -0
  103. package/src/refactoring/boundary-extraction-strategies.js +330 -0
  104. package/src/refactoring/boundary-extractor.js +384 -0
  105. package/src/refactoring/boundary-types.js +46 -0
  106. package/src/refactoring/circular/circular-dependency.js +88 -0
  107. package/src/refactoring/circular/cycle-detection.js +147 -0
  108. package/src/refactoring/circular/dependency-node.js +82 -0
  109. package/src/refactoring/circular/dependency-result.js +107 -0
  110. package/src/refactoring/circular/dependency-types.js +58 -0
  111. package/src/refactoring/circular/graph-builder.js +213 -0
  112. package/src/refactoring/circular/resolution-strategy.js +72 -0
  113. package/src/refactoring/circular/strategy-generator.js +229 -0
  114. package/src/refactoring/circular-dependency-resolver-original.js +809 -0
  115. package/src/refactoring/circular-dependency-resolver.js +200 -0
  116. package/src/refactoring/code-mover.js +761 -0
  117. package/src/refactoring/file-splitter.js +696 -0
  118. package/src/refactoring/functionality-validator.js +816 -0
  119. package/src/refactoring/import-manager.js +774 -0
  120. package/src/refactoring/module-boundary.js +107 -0
  121. package/src/refactoring/refactoring-executor.js +672 -0
  122. package/src/refactoring/refactoring-rollback.js +614 -0
  123. package/src/refactoring/test-validator.js +631 -0
  124. package/src/requirement-management/default-requirement-manager.js +321 -0
  125. package/src/requirement-management/requirement-file-parser.js +159 -0
  126. package/src/requirement-management/requirement-sequencer.js +221 -0
  127. package/src/rui/commands/AgentCommandParser.js +600 -0
  128. package/src/rui/commands/AgentCommands.js +487 -0
  129. package/src/rui/commands/AgentResponseFormatter.js +832 -0
  130. package/src/scripts/verify-full-compliance.js +269 -0
  131. package/src/sync/sync-engine-core.js +1 -0
  132. package/src/sync/sync-engine-remote-handlers.js +135 -0
  133. package/src/task-generation/automated-task-generator.js +351 -0
  134. package/src/task-generation/prioritizer.js +287 -0
  135. package/src/task-generation/task-list-updater.js +215 -0
  136. package/src/task-generation/task-management-integration.js +480 -0
  137. package/src/task-generation/task-manager-integration.js +270 -0
  138. package/src/task-generation/violation-task-generator.js +474 -0
  139. package/src/task-management/continuous-scan-integration.js +342 -0
  140. package/src/timeout-management/index.js +12 -3
  141. package/src/timeout-management/response-time-tracker.js +167 -0
  142. package/src/timeout-management/timeout-calculator.js +159 -0
  143. package/src/timeout-management/timeout-config-manager.js +172 -0
  144. package/src/utils/ast-analyzer.js +417 -0
  145. package/src/utils/current-requirement-manager.js +276 -0
  146. package/src/utils/current-requirement-operations.js +472 -0
  147. package/src/utils/dependency-mapper.js +456 -0
  148. package/src/utils/download-with-progress.js +4 -2
  149. package/src/utils/electron-update-checker.js +4 -1
  150. package/src/utils/file-size-analyzer.js +272 -0
  151. package/src/utils/import-updater.js +280 -0
  152. package/src/utils/refactoring-tools.js +512 -0
  153. package/src/utils/report-generator.js +569 -0
  154. package/src/utils/reports/report-analysis.js +218 -0
  155. package/src/utils/reports/report-types.js +55 -0
  156. package/src/utils/reports/summary-generators.js +102 -0
  157. package/src/utils/requirement-file-management.js +157 -0
  158. package/src/utils/requirement-helpers/requirement-file-ops.js +392 -0
  159. package/src/utils/requirement-helpers/requirement-mover.js +414 -0
  160. package/src/utils/requirement-helpers/requirement-parser.js +326 -0
  161. package/src/utils/requirement-helpers/requirement-status.js +320 -0
  162. package/src/utils/requirement-helpers-new.js +55 -0
  163. package/src/utils/requirement-helpers-refactored.js +367 -0
  164. package/src/utils/requirement-helpers.js +291 -1191
  165. package/src/utils/requirement-movement-operations.js +450 -0
  166. package/src/utils/requirement-movement.js +312 -0
  167. package/src/utils/requirement-parsing-helpers.js +56 -0
  168. package/src/utils/requirement-statistics.js +200 -0
  169. package/src/utils/requirement-text-utils.js +58 -0
  170. package/src/utils/rollback/rollback-handlers.js +125 -0
  171. package/src/utils/rollback/rollback-operation.js +63 -0
  172. package/src/utils/rollback/rollback-recorder.js +166 -0
  173. package/src/utils/rollback/rollback-state-manager.js +175 -0
  174. package/src/utils/rollback/rollback-types.js +33 -0
  175. package/src/utils/rollback/rollback-utils.js +110 -0
  176. package/src/utils/rollback-manager-original.js +569 -0
  177. package/src/utils/rollback-manager.js +202 -0
  178. package/src/utils/smoke-test-cli.js +362 -0
  179. package/src/utils/smoke-test-gui.js +351 -0
  180. package/src/utils/smoke-test-orchestrator.js +321 -0
  181. package/src/utils/smoke-test-runner.js +60 -0
  182. package/src/utils/smoke-test-web.js +347 -0
  183. package/src/utils/specification-helpers.js +39 -13
  184. package/src/utils/specification-migration.js +97 -0
  185. package/src/utils/test-runner.js +579 -0
  186. package/src/utils/validation-framework.js +518 -0
  187. package/src/validation/compliance-analyzer.js +197 -0
  188. package/src/validation/compliance-report-generator.js +343 -0
  189. package/src/validation/compliance-reporter.js +711 -0
  190. package/src/validation/compliance-rules.js +127 -0
  191. package/src/validation/constitution-validator-new.js +196 -0
  192. package/src/validation/constitution-validator.js +17 -0
  193. package/src/validation/file-validators.js +170 -0
  194. package/src/validation/line-limit/file-analyzer.js +201 -0
  195. package/src/validation/line-limit/line-limit-validator.js +208 -0
  196. package/src/validation/line-limit/validation-result.js +144 -0
  197. package/src/validation/line-limit-core.js +225 -0
  198. package/src/validation/line-limit-reporter.js +134 -0
  199. package/src/validation/line-limit-result.js +125 -0
  200. package/src/validation/line-limit-validator.js +41 -0
  201. package/src/validation/metrics-calculator.js +660 -0
  202. package/src/sync/sync-engine-backup.js +0 -559
@@ -1,51 +1,19 @@
1
- // @vibecodingmachine/core - AppleScript Manager
1
+ // @vibecodingmachine/core - AppleScript Manager (Refactored)
2
2
  // Handles AppleScript-based interactions with IDEs that don't support CDP
3
3
  // Now supports both macOS (AppleScript) and Windows (PowerShell automation)
4
4
 
5
- const { execSync, spawn } = 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');
5
+ // Import the refactored modules
6
+ import AppleScriptManagerCore from './applescript-manager-core.js';
17
7
 
18
8
  /**
19
- * AppleScript Manager for IDE interactions
20
- * Handles AppleScript-based text sending and response reading for IDEs like Cursor and Windsurf
9
+ * AppleScript Manager for IDE interactions (Refactored)
10
+ * This is now a thin wrapper around the modular core functionality
11
+ * Original file was 4,092 lines, now split into focused modules under 555 lines each
21
12
  */
22
13
  class AppleScriptManager {
23
14
  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
- * Escape text for AppleScript string literals
35
- * @param {string} text - Text to escape
36
- * @returns {string} Escaped text safe for AppleScript
37
- */
38
- escapeAppleScriptString(text) {
39
- if (typeof text !== 'string') {
40
- return '';
41
- }
42
-
43
- return text
44
- .replace(/\\/g, '\\\\') // Escape backslashes first
45
- .replace(/"/g, '\\"') // Escape double quotes
46
- .replace(/\n/g, '\\n') // Escape newlines
47
- .replace(/\r/g, '\\r') // Escape carriage returns
48
- .replace(/\t/g, '\\t'); // Escape tabs
15
+ // Delegate to the core manager which handles all platform-specific logic
16
+ this.coreManager = new AppleScriptManagerCore();
49
17
  }
50
18
 
51
19
  /**
@@ -54,107 +22,7 @@ class AppleScriptManager {
54
22
  * @returns {Promise<Object>} Result with success and note
55
23
  */
56
24
  async sendDeterministicToTerminalWindow1(text) {
57
- // CRITICAL: Validate text parameter
58
- if (!text || typeof text !== 'string') {
59
- this.logger.error(`❌ [Deterministic] Invalid text parameter: type=${typeof text}, value=${text}`);
60
- return {
61
- success: false,
62
- error: `Invalid text parameter: received ${typeof text} instead of string`,
63
- debug: { textType: typeof text, textValue: text }
64
- };
65
- }
66
-
67
- try {
68
- // First, read recent history to detect interactive prompts (like theme selection)
69
- const readHistoryScript = `
70
- tell application "Terminal"
71
- try
72
- set fullHistory to history of tab 1 of window 1 as text
73
- if (length of fullHistory) > 600 then
74
- set histSnippet to (text -600 thru -1 of fullHistory)
75
- else
76
- set histSnippet to fullHistory
77
- end if
78
- return histSnippet
79
- on error
80
- return "HIST_READ_ERROR"
81
- end try
82
- end tell
83
- `;
84
-
85
- const tmpRead = join(tmpdir(), `claude_read_${Date.now()}.scpt`);
86
- writeFileSync(tmpRead, readHistoryScript, 'utf8');
87
- let historyOut = '';
88
- try {
89
- historyOut = execSync(`osascript "${tmpRead}"`, { encoding: 'utf8', timeout: 10000, stdio: 'pipe' }).trim();
90
- } catch (rerr) {
91
- this.logger.log('⚠️ Deterministic: failed to read history before send:', rerr.message || rerr);
92
- historyOut = '';
93
- } finally { try { unlinkSync(tmpRead); } catch (e) { } }
94
-
95
- this.logger.log('🔎 Deterministic: recent history snippet length:', historyOut.length);
96
-
97
- // Detect interactive prompt patterns that commonly appear on first run (e.g., theme selection)
98
- const interactivePatterns = [
99
- 'Choose the text style',
100
- 'Change this later, run /theme',
101
- '1. Dark mode',
102
- '2. Light mode',
103
- 'Choose the text style that looks best',
104
- 'To change this later, run /theme'
105
- ];
106
-
107
- const hasInteractivePrompt = interactivePatterns.some(p => historyOut.includes(p));
108
- this.logger.log('🔎 Deterministic: interactive prompt detected=', hasInteractivePrompt);
109
-
110
- const directSendScript = `
111
- tell application "Terminal"
112
- activate
113
- try
114
- set selected tab of window 1 to tab 1 of window 1
115
- delay 0.5
116
- end try
117
- end tell
118
-
119
- tell application "System Events"
120
- tell process "Terminal"
121
- set frontmost to true
122
- delay 0.5
123
- ${hasInteractivePrompt ? `keystroke return\n delay 0.8` : ''}
124
- keystroke "${text.replace(/"/g, '\\"')}"
125
- delay 1.0
126
- keystroke return
127
- delay 0.3
128
- end tell
129
- end tell
130
-
131
- tell application "Terminal"
132
- try
133
- set fullHistory to history of tab 1 of window 1 as text
134
- if (length of fullHistory) > 300 then
135
- set histSnippet to (text -300 thru -1 of fullHistory)
136
- else
137
- set histSnippet to fullHistory
138
- end if
139
- return "SENT_TO:W1_T1|HIST:" & histSnippet
140
- on error
141
- return "SENT_TO:W1_T1|HIST:ERROR"
142
- end try
143
- end tell
144
- `;
145
-
146
- const tempFile = join(tmpdir(), `claude_direct_${Date.now()}.scpt`);
147
- writeFileSync(tempFile, directSendScript, 'utf8');
148
- const out = execSync(`osascript "${tempFile}"`, { encoding: 'utf8', timeout: 30000, stdio: 'pipe' }).trim();
149
- try { unlinkSync(tempFile); } catch (e) { }
150
-
151
- this.logger.log('🔁 Deterministic direct send output:', out);
152
- const verified = out.indexOf('|HIST:') !== -1 && out.split('|HIST:')[1].includes(text);
153
- return { success: verified, note: out };
154
- } catch (err) {
155
- this.logger.log('❌ Deterministic direct send failed:', err.message || err);
156
- return { success: false, error: err.message || String(err) };
157
- }
25
+ return await this.coreManager.sendDeterministicToTerminalWindow1(text);
158
26
  }
159
27
 
160
28
  /**
@@ -163,41 +31,7 @@ class AppleScriptManager {
163
31
  * @returns {Promise<Object>} Result object with success status and details
164
32
  */
165
33
  async openCursor(repoPath = null) {
166
- // Handle Windows platform
167
- if (this.platform === 'win32' && this.windowsManager) {
168
- return await this.windowsManager.openCursor(repoPath);
169
- }
170
-
171
- // Handle macOS platform (existing AppleScript logic)
172
- try {
173
- this.logger.log('Opening Cursor with remote debugging enabled...');
174
-
175
- // Launch Cursor with remote debugging enabled
176
- let command = 'open -a "Cursor" --args --remote-debugging-port=9225';
177
- if (repoPath) {
178
- command = `open -a "Cursor" "${repoPath}" --args --remote-debugging-port=9225`;
179
- }
180
-
181
- execSync(command, { stdio: 'pipe' });
182
-
183
- // Wait for Cursor to start and bind the debugging port
184
- await new Promise(resolve => setTimeout(resolve, 3000));
185
-
186
- this.logger.log('Cursor opened successfully with remote debugging on port 9225');
187
- return {
188
- success: true,
189
- message: repoPath ? `Cursor opened with repository: ${repoPath} and remote debugging` : 'Cursor opened successfully with remote debugging',
190
- method: 'applescript',
191
- debugPort: 9225
192
- };
193
- } catch (error) {
194
- this.logger.log('Error opening Cursor:', error.message);
195
- return {
196
- success: false,
197
- error: error.message,
198
- method: 'applescript'
199
- };
200
- }
34
+ return await this.coreManager.openIDE('cursor', repoPath);
201
35
  }
202
36
 
203
37
  /**
@@ -206,8 +40,7 @@ class AppleScriptManager {
206
40
  * @returns {Promise<Object>} Result object with success status and details
207
41
  */
208
42
  async openGemini(repoPath = null) {
209
- this.logger.log('Opening Gemini, which runs in VS Code...');
210
- return await this.openVSCode(repoPath);
43
+ return await this.coreManager.openIDE('gemini', repoPath);
211
44
  }
212
45
 
213
46
  /**
@@ -216,162 +49,7 @@ class AppleScriptManager {
216
49
  * @returns {Promise<Object>} Result object with success status and details
217
50
  */
218
51
  async openClaude(repoPath = null) {
219
- try {
220
- this.logger.log('Opening Claude in terminal or bringing to foreground...');
221
-
222
- // First, check if any Claude process is running using a simple shell command
223
- try {
224
- const claudeProcessCheck = execSync('ps aux | grep -i "claude" | grep -v grep | grep -v "Claude.app" | wc -l', { encoding: 'utf8' }).trim();
225
- const claudeCount = parseInt(claudeProcessCheck);
226
- this.logger.log(`🔍 [Claude] Found ${claudeCount} Claude processes running`);
227
-
228
- if (claudeCount === 0) {
229
- this.logger.log('No Claude process found, will create new terminal');
230
- throw new Error('No Claude process running');
231
- }
232
- } catch (processCheckError) {
233
- this.logger.log('No Claude process detected, will create new terminal');
234
- // Jump to creating new terminal
235
- const escapedPath = repoPath ? repoPath.replace(/'/g, "'\\''") : '';
236
- const cdCommand = repoPath ? `cd '${escapedPath}' && ` : '';
237
- const openClaudeScript = `
238
- tell application "Terminal"
239
- activate
240
- set newWindow to do script "echo 'Starting Claude Code...' && ${cdCommand}claude --dangerously-skip-permissions"
241
- delay 3
242
- end tell
243
- `;
244
-
245
- const tempFile = join(tmpdir(), `claude_open_${Date.now()}.scpt`);
246
- try {
247
- writeFileSync(tempFile, openClaudeScript, 'utf8');
248
- execSync(`osascript "${tempFile}"`, { stdio: 'pipe' });
249
-
250
- this.logger.log('Claude opened in new terminal successfully');
251
- return {
252
- success: true,
253
- message: repoPath ? `Claude opened in terminal with context: ${repoPath}` : 'Claude opened in new terminal',
254
- method: 'applescript',
255
- action: 'open'
256
- };
257
- } finally {
258
- try {
259
- unlinkSync(tempFile);
260
- } catch (cleanupError) {
261
- this.logger.log(`⚠️ Failed to cleanup temp file: ${cleanupError.message}`);
262
- }
263
- }
264
- }
265
-
266
- // If we get here, Claude process exists - find the specific Claude terminal window
267
- try {
268
- this.logger.log('🔍 [Claude] Claude process detected, searching for Claude terminal window...');
269
- const escapedRepoPath = repoPath ? repoPath.replace(/'/g, "'\\''") : '';
270
- const repoPathCheck = repoPath ? `and historyText contains "${escapedRepoPath}"` : '';
271
- const findClaudeWindowScript = `
272
- tell application "Terminal"
273
- activate
274
- delay 0.5
275
-
276
- set claudeWindow to null
277
- set windowCount to count of windows
278
-
279
- -- Search through all terminal windows to find the one running Claude IN THE CORRECT DIRECTORY
280
- repeat with i from 1 to windowCount
281
- try
282
- set currentWindow to window i
283
- set currentTab to selected tab of currentWindow
284
- set historyText to history of currentTab as text
285
-
286
- -- Check for Claude AND optionally the correct repo path to avoid wrong terminal
287
- -- CRITICAL FIX: Must check BOTH Claude process AND repo path to avoid wrong terminal
288
- if (historyText contains "Claude Code" or historyText contains "claude --dangerously-skip-permissions") ${repoPathCheck} then
289
- set claudeWindow to currentWindow
290
- exit repeat
291
- end if
292
- on error
293
- -- Continue to next window
294
- end try
295
- end repeat
296
-
297
- if claudeWindow is not null then
298
- -- Found Claude window - bring it to front
299
- set index of claudeWindow to 1
300
- delay 0.5
301
- return "claude-window-found"
302
- else
303
- return "claude-window-not-found"
304
- end if
305
- end tell
306
- `;
307
-
308
- const tempFile = join(tmpdir(), `claude_find_${Date.now()}.scpt`);
309
- try {
310
- writeFileSync(tempFile, findClaudeWindowScript, 'utf8');
311
- const result = execSync(`osascript "${tempFile}"`, { stdio: 'pipe', encoding: 'utf8' }).trim();
312
-
313
- if (result === 'claude-window-found') {
314
- this.logger.log('✅ [Claude] Found and focused Claude terminal window');
315
- return {
316
- success: true,
317
- message: 'Claude terminal window found and focused',
318
- method: 'applescript',
319
- action: 'focus'
320
- };
321
- } else {
322
- this.logger.log('⚠️ [Claude] Claude window not found despite process running, will create new one');
323
- }
324
- } finally {
325
- try {
326
- unlinkSync(tempFile);
327
- } catch (cleanupError) {
328
- this.logger.log(`⚠️ Failed to cleanup temp file: ${cleanupError.message}`);
329
- }
330
- }
331
- } catch (focusError) {
332
- this.logger.log('⚠️ [Claude] Error finding Claude window, will create new one:', focusError.message);
333
- }
334
-
335
- // No existing Claude found, open a new terminal with Claude
336
- // Escape the path properly for AppleScript
337
- const escapedPath = repoPath ? repoPath.replace(/'/g, "'\\''") : '';
338
- const cdCommand = repoPath ? `cd '${escapedPath}' && ` : '';
339
- const openClaudeScript = `
340
- tell application "Terminal"
341
- activate
342
- set newWindow to do script "echo 'Starting Claude Code...' && ${cdCommand}claude --dangerously-skip-permissions"
343
- delay 3
344
- end tell
345
- `;
346
-
347
- // Use file-based execution to avoid quote escaping issues
348
- const tempFile = join(tmpdir(), `claude_open_${Date.now()}.scpt`);
349
- try {
350
- writeFileSync(tempFile, openClaudeScript, 'utf8');
351
- execSync(`osascript "${tempFile}"`, { stdio: 'pipe' });
352
-
353
- this.logger.log('Claude opened in new terminal successfully');
354
- return {
355
- success: true,
356
- message: repoPath ? `Claude opened in terminal with context: ${repoPath}` : 'Claude opened in new terminal',
357
- method: 'applescript',
358
- action: 'open'
359
- };
360
- } finally {
361
- try {
362
- unlinkSync(tempFile);
363
- } catch (cleanupError) {
364
- this.logger.log(`⚠️ Failed to cleanup temp file: ${cleanupError.message}`);
365
- }
366
- }
367
- } catch (error) {
368
- this.logger.log('Error opening Claude:', error.message);
369
- return {
370
- success: false,
371
- error: error.message,
372
- method: 'applescript'
373
- };
374
- }
52
+ return await this.coreManager.openIDE('claude', repoPath);
375
53
  }
376
54
 
377
55
  /**
@@ -380,56 +58,7 @@ class AppleScriptManager {
380
58
  * @returns {Promise<Object>} Result object with success status and details
381
59
  */
382
60
  async openVSCode(repoPath = null) {
383
- // Handle Windows platform
384
- if (this.platform === 'win32' && this.windowsManager) {
385
- // Assuming a similar method exists in the windows manager
386
- return await this.windowsManager.openVSCode(repoPath);
387
- }
388
-
389
- // Handle macOS platform
390
- try {
391
- this.logger.log('Opening VS Code with remote debugging enabled...');
392
-
393
- // First, check if VS Code is already running
394
- try {
395
- const isRunning = execSync('pgrep -x "Code"', { encoding: 'utf8', stdio: 'pipe' }).trim();
396
- if (isRunning) {
397
- this.logger.log('VS Code is already running - closing it to enable remote debugging...');
398
- execSync('pkill -x "Code"', { stdio: 'pipe' });
399
- await new Promise(resolve => setTimeout(resolve, 1000));
400
- }
401
- } catch (err) {
402
- // VS Code not running, that's fine
403
- }
404
-
405
- // Launch VS Code with remote debugging enabled for quota detection
406
- let command = 'open -a "Visual Studio Code" --args --remote-debugging-port=9222';
407
- if (repoPath) {
408
- command = `open -a "Visual Studio Code" "${repoPath}" --args --remote-debugging-port=9222`;
409
- this.logger.log(`Opening VS Code with repository: ${repoPath}`);
410
- }
411
-
412
- execSync(command, { stdio: 'pipe' });
413
-
414
- await new Promise(resolve => setTimeout(resolve, 3000));
415
-
416
- // Verify remote debugging port is accessible
417
- try {
418
- const CDP = require('chrome-remote-interface');
419
- await CDP.List({ port: 9222 });
420
- this.logger.log('✅ VS Code remote debugging port 9222 is accessible');
421
- } catch (cdpError) {
422
- this.logger.log(`⚠️ Warning: VS Code remote debugging port not accessible: ${cdpError.message}`);
423
- this.logger.log(' Waiting additional 2 seconds for VS Code to fully start...');
424
- await new Promise(resolve => setTimeout(resolve, 2000));
425
- }
426
-
427
- this.logger.log('VS Code opened successfully with remote debugging');
428
- return { success: true, message: `VS Code opened with repository: ${repoPath}`, method: 'applescript' };
429
- } catch (error) {
430
- this.logger.log('Error opening VS Code:', error.message);
431
- return { success: false, error: error.message, method: 'applescript' };
432
- }
61
+ return await this.coreManager.openIDE('vscode', repoPath);
433
62
  }
434
63
 
435
64
  /**
@@ -439,77 +68,7 @@ class AppleScriptManager {
439
68
  * @returns {Promise<Object>} Result object with success status and details
440
69
  */
441
70
  async openVSCodeWithExtension(extension, repoPath = null) {
442
- try {
443
- this.logger.log(`Opening VS Code with ${extension} extension...`);
444
-
445
- // Extension IDs mapping
446
- const extensionIds = {
447
- 'github-copilot': 'GitHub.copilot',
448
- 'amazon-q': 'amazonwebservices.amazon-q-vscode',
449
- 'continue': 'Continue.continue'
450
- };
451
-
452
- const extensionId = extensionIds[extension];
453
- if (!extensionId) {
454
- return { success: false, error: `Unknown extension: ${extension}`, method: 'applescript' };
455
- }
456
-
457
- // First, check if the extension is installed
458
- try {
459
- const checkCmd = `code --list-extensions | grep -i "${extensionId}"`;
460
- execSync(checkCmd, { stdio: 'pipe' });
461
- this.logger.log(`Extension ${extension} is already installed`);
462
- } catch (checkError) {
463
- // Extension not found, try to install it
464
- this.logger.log(`Extension ${extension} not found, installing...`);
465
- try {
466
- execSync(`code --install-extension ${extensionId} --force`, { stdio: 'pipe', timeout: 60000 });
467
- this.logger.log(`Extension ${extension} installed successfully`);
468
- } catch (installError) {
469
- this.logger.log(`Failed to install extension ${extension}:`, installError.message);
470
- return { success: false, error: `Failed to install extension: ${installError.message}`, method: 'applescript' };
471
- }
472
- }
473
-
474
- // First, check if VS Code is already running
475
- try {
476
- const isRunning = execSync('pgrep -x "Code"', { encoding: 'utf8', stdio: 'pipe' }).trim();
477
- if (isRunning) {
478
- this.logger.log('VS Code is already running - closing it to enable remote debugging...');
479
- execSync('pkill -x "Code"', { stdio: 'pipe' });
480
- await new Promise(resolve => setTimeout(resolve, 1000));
481
- }
482
- } catch (err) {
483
- // VS Code not running, that's fine
484
- }
485
-
486
- // Open VS Code with the repository and remote debugging enabled
487
- let command = 'open -a "Visual Studio Code" --args --remote-debugging-port=9222';
488
- if (repoPath) {
489
- command = `open -a "Visual Studio Code" "${repoPath}" --args --remote-debugging-port=9222`;
490
- this.logger.log(`Opening VS Code with repository: ${repoPath}`);
491
- }
492
-
493
- execSync(command, { stdio: 'pipe' });
494
- await new Promise(resolve => setTimeout(resolve, 3000));
495
-
496
- // Verify remote debugging port is accessible
497
- try {
498
- const CDP = require('chrome-remote-interface');
499
- await CDP.List({ port: 9222 });
500
- this.logger.log('✅ VS Code remote debugging port 9222 is accessible');
501
- } catch (cdpError) {
502
- this.logger.log(`⚠️ Warning: VS Code remote debugging port not accessible: ${cdpError.message}`);
503
- this.logger.log(' Waiting additional 2 seconds for VS Code to fully start...');
504
- await new Promise(resolve => setTimeout(resolve, 2000));
505
- }
506
-
507
- this.logger.log(`VS Code opened successfully with ${extension} extension and remote debugging`);
508
- return { success: true, message: `VS Code opened with ${extension} extension`, method: 'applescript' };
509
- } catch (error) {
510
- this.logger.log(`Error opening VS Code with ${extension}:`, error.message);
511
- return { success: false, error: error.message, method: 'applescript' };
512
- }
71
+ return await this.coreManager.openIDE(extension, repoPath);
513
72
  }
514
73
 
515
74
  /**
@@ -518,163 +77,25 @@ class AppleScriptManager {
518
77
  * @returns {Promise<Object>} Result object with success status and details
519
78
  */
520
79
  async openReplit(repoPath = null) {
521
- try {
522
- this.logger.log('Opening Replit...');
523
- this.logger.log(`Platform detected: ${this.platform}`);
524
-
525
- // Replit typically uses web browser, so we'll open the Replit website
526
- // If Replit has a desktop app or CLI, this can be updated
527
- const replitUrl = repoPath
528
- ? `https://replit.com/@replit/agent?path=${encodeURIComponent(repoPath)}`
529
- : 'https://replit.com/@replit/agent';
530
-
531
- let command;
532
- if (this.platform === 'darwin') {
533
- command = `open "${replitUrl}"`;
534
- this.logger.log(`Using macOS command: ${command}`);
535
- } else if (this.platform === 'win32') {
536
- command = `start "" "${replitUrl}"`;
537
- this.logger.log(`Using Windows command: ${command}`);
538
- } else {
539
- command = `xdg-open "${replitUrl}"`;
540
- this.logger.log(`Using Linux command: ${command}`);
541
- }
542
-
543
- execSync(command, { stdio: 'pipe' });
544
- await new Promise(resolve => setTimeout(resolve, 1000));
545
-
546
- this.logger.log('Replit opened successfully');
547
- return { success: true, message: 'Replit opened in browser', method: 'applescript' };
548
- } catch (error) {
549
- this.logger.log('Error opening Replit:', error.message);
550
- return { success: false, error: error.message, method: 'applescript' };
551
- }
80
+ return await this.coreManager.openIDE('replit', repoPath);
552
81
  }
553
82
 
554
-
555
83
  /**
556
84
  * Open Windsurf IDE with optional repository path
557
85
  * @param {string} repoPath - Optional repository path to open
558
86
  * @returns {Promise<Object>} Result object with success status and details
559
87
  */
560
88
  async openWindsurf(repoPath = null) {
561
- try {
562
- this.logger.log('Opening Windsurf...');
563
-
564
- // Try to find Windsurf installation
565
- const possiblePaths = [
566
- '/Users/jesseolsen/.codeium/windsurf/bin/windsurf', // macOS - Codeium installation
567
- '/Applications/Windsurf.app/Contents/MacOS/Windsurf', // macOS - App Store
568
- 'C:\\Users\\%USERNAME%\\AppData\\Local\\Programs\\Windsurf\\Windsurf.exe', // Windows
569
- '/usr/local/bin/windsurf', // Linux
570
- '/opt/windsurf/windsurf' // Linux alternative
571
- ];
572
-
573
- let windsurfPath = possiblePaths.find(path => {
574
- try {
575
- const fs = require('fs');
576
- fs.accessSync(path.replace('%USERNAME%', process.env.USERNAME || process.env.USER));
577
- return true;
578
- } catch {
579
- return false;
580
- }
581
- });
582
-
583
- if (!windsurfPath) {
584
- // Try using the 'windsurf' command directly
585
- windsurfPath = 'windsurf';
586
- }
587
-
588
- let command;
589
- if (windsurfPath.includes('Windsurf.app')) {
590
- // macOS app bundle
591
- command = `open -a "Windsurf"`;
592
- if (repoPath) {
593
- command += ` "${repoPath}"`;
594
- }
595
- } else {
596
- // Direct executable - use --reuse-window flag to avoid hanging when Windsurf is already running
597
- command = `"${windsurfPath}" --reuse-window`;
598
- if (repoPath) {
599
- command += ` "${repoPath}"`;
600
- }
601
- }
602
-
603
- this.logger.log(`Executing command: ${command}`);
604
-
605
- // For Windsurf, we don't wait for the command to complete since it runs in background
606
- if (windsurfPath.includes('windsurf')) {
607
- // Use spawn to run in background
608
- const { spawn } = require('child_process');
609
- const windsurfProcess = spawn(windsurfPath, ['--reuse-window', repoPath], {
610
- stdio: 'pipe',
611
- detached: true
612
- });
613
-
614
- // Don't wait for completion, just give it a moment to start
615
- await new Promise(resolve => setTimeout(resolve, 1000));
616
- } else {
617
- execSync(command, { stdio: 'pipe' });
618
- // Wait a moment for Windsurf to start
619
- await new Promise(resolve => setTimeout(resolve, 2000));
620
- }
621
-
622
- this.logger.log('Windsurf opened successfully');
623
- return {
624
- success: true,
625
- message: repoPath ? `Windsurf opened with repository: ${repoPath}` : 'Windsurf opened successfully',
626
- method: 'applescript'
627
- };
628
- } catch (error) {
629
- this.logger.log('Error opening Windsurf:', error.message);
630
- return {
631
- success: false,
632
- error: error.message,
633
- method: 'applescript'
634
- };
635
- }
89
+ return await this.coreManager.openIDE('windsurf', repoPath);
636
90
  }
637
91
 
92
+ /**
93
+ * Open Google Antigravity with optional repository path
94
+ * @param {string} repoPath - Optional repository path to open
95
+ * @returns {Promise<Object>} Result object with success status and details
96
+ */
638
97
  async openAntigravity(repoPath = null) {
639
- try {
640
- this.logger.log('Opening Google Antigravity...');
641
-
642
- // Google Antigravity app path
643
- const antigravityPath = '/Applications/Antigravity.app';
644
-
645
- // Check if Antigravity is installed
646
- try {
647
- const fs = require('fs');
648
- fs.accessSync(antigravityPath);
649
- } catch {
650
- throw new Error('Google Antigravity is not installed at /Applications/Antigravity.app');
651
- }
652
-
653
- let command = `open -a "Antigravity"`;
654
- if (repoPath) {
655
- command += ` "${repoPath}"`;
656
- }
657
-
658
- this.logger.log(`Executing command: ${command}`);
659
- execSync(command, { stdio: 'pipe' });
660
-
661
- // Wait a moment for Antigravity to start
662
- await new Promise(resolve => setTimeout(resolve, 2000));
663
-
664
- this.logger.log('Google Antigravity opened successfully');
665
- return {
666
- success: true,
667
- message: repoPath ? `Google Antigravity opened with repository: ${repoPath}` : 'Google Antigravity opened successfully',
668
- method: 'applescript'
669
- };
670
- } catch (error) {
671
- this.logger.log('Error opening Google Antigravity:', error.message);
672
- return {
673
- success: false,
674
- error: error.message,
675
- method: 'applescript'
676
- };
677
- }
98
+ return await this.coreManager.openIDE('antigravity', repoPath);
678
99
  }
679
100
 
680
101
  /**
@@ -683,294 +104,32 @@ class AppleScriptManager {
683
104
  * @returns {Promise<Object>} Result object with success status and details
684
105
  */
685
106
  async openKiro(repoPath = null) {
686
- try {
687
- this.logger.log('Opening AWS Kiro...');
688
-
689
- let command = 'open -a "AWS Kiro"';
690
- if (repoPath) {
691
- command += ` "${repoPath}"`;
692
- }
693
-
694
- try {
695
- this.logger.log(`Executing command: ${command}`);
696
- execSync(command, { stdio: 'pipe' });
697
- } catch (e) {
698
- // Fallback to just "Kiro"
699
- this.logger.log('Failed to open "AWS Kiro", trying "Kiro"...');
700
- command = 'open -a "Kiro"';
701
- if (repoPath) {
702
- command += ` "${repoPath}"`;
703
- }
704
- execSync(command, { stdio: 'pipe' });
705
- }
706
-
707
- // Wait a moment for Kiro to start
708
- await new Promise(resolve => setTimeout(resolve, 3000));
709
-
710
- this.logger.log('AWS Kiro opened successfully');
711
- return {
712
- success: true,
713
- message: repoPath ? `AWS Kiro opened with repository: ${repoPath}` : 'AWS Kiro opened successfully',
714
- method: 'applescript'
715
- };
716
- } catch (error) {
717
- this.logger.log('Error opening AWS Kiro:', error.message);
718
- return {
719
- success: false,
720
- error: error.message,
721
- method: 'applescript'
722
- };
723
- }
107
+ return await this.coreManager.openIDE('kiro', repoPath);
724
108
  }
725
109
 
726
110
  /**
727
- * Check Antigravity UI for Gemini quota limit popup text.
728
- * This is best-effort: Antigravity may render the popup in a WebView that AppleScript can't read.
729
- * @returns {Promise<{isRateLimited: boolean, message?: string, resumeAt?: string, note?: string}>}
111
+ * Detect continuation prompts using AppleScript UI queries
112
+ * @returns {Promise<boolean>} True if continuation prompt detected
730
113
  */
731
- async checkAntigravityQuotaLimit() {
732
- try {
733
- const script = `
734
- tell application "System Events"
735
- if not (exists process "Antigravity") then
736
- return "NOT_RUNNING"
737
- end if
738
- tell process "Antigravity"
739
- set frontmost to true
740
- delay 0.4
741
-
742
- set allText to ""
743
-
744
- -- Check ALL windows (quota popup may not be window 1)
745
- set windowCount to count of windows
746
- repeat with w from 1 to windowCount
747
- try
748
- -- Window title can reveal quota dialogs
749
- set wTitle to name of window w
750
- set allText to allText & wTitle & "\\n"
751
- end try
752
-
753
- try
754
- set allText to allText & (value of static text of window w as string) & "\\n"
755
- end try
756
-
757
- try
758
- set groupsList to groups of window w
759
- repeat with g in groupsList
760
- try
761
- set allText to allText & (value of static text of g as string) & "\\n"
762
- end try
763
- -- Also check one level of sub-groups
764
- try
765
- set subGroups to groups of g
766
- repeat with sg in subGroups
767
- try
768
- set allText to allText & (value of static text of sg as string) & "\\n"
769
- end try
770
- end repeat
771
- end try
772
- -- Check buttons inside groups (quota popups often have "Select another model" buttons)
773
- try
774
- set groupButtons to buttons of g
775
- repeat with btn in groupButtons
776
- try
777
- set allText to allText & (name of btn) & "\\n"
778
- end try
779
- end repeat
780
- end try
781
- end repeat
782
- end try
783
-
784
- -- Check button names at window level (quota actions sometimes appear as buttons)
785
- try
786
- set buttonList to buttons of window w
787
- repeat with btn in buttonList
788
- try
789
- set allText to allText & (name of btn) & "\\n"
790
- end try
791
- end repeat
792
- end try
793
-
794
- -- Check sheets (modal dialogs presented as sheets)
795
- try
796
- set sheetList to sheets of window w
797
- repeat with s in sheetList
798
- try
799
- set allText to allText & (value of static text of s as string) & "\\n"
800
- end try
801
- try
802
- set sheetGroups to groups of s
803
- repeat with sg in sheetGroups
804
- try
805
- set allText to allText & (value of static text of sg as string) & "\\n"
806
- end try
807
- end repeat
808
- end try
809
- end repeat
810
- end try
811
-
812
- -- Check scroll areas
813
- try
814
- set scrollAreas to scroll areas of window w
815
- repeat with sa in scrollAreas
816
- try
817
- set allText to allText & (value of static text of sa as string) & "\\n"
818
- end try
819
- end repeat
820
- end try
821
- end repeat
822
-
823
- return allText
824
- end tell
825
- end tell
826
- `;
827
-
828
- const raw = execSync(`osascript -e '${script.replace(/'/g, "'\\\\''")}'`, {
829
- encoding: 'utf8',
830
- timeout: 10000
831
- }).trim();
832
-
833
- if (!raw || raw === 'NOT_RUNNING') {
834
- return {
835
- isRateLimited: false,
836
- note: raw === 'NOT_RUNNING' ? 'Antigravity not running' : 'No readable UI text'
837
- };
838
- }
839
-
840
- const text = raw.replace(/\s+/g, ' ').trim();
841
- const lower = text.toLowerCase();
842
-
843
- const hasGeminiQuota = lower.includes('model quota limit exceeded') ||
844
- (lower.includes('quota limit') && lower.includes('you can resume')) ||
845
- lower.includes('you have reached the quota limit') ||
846
- lower.includes('spending cap reached') ||
847
- lower.includes('usage cap reached') ||
848
- lower.includes('rate limit reached') ||
849
- lower.includes('resource has been exhausted') ||
850
- lower.includes('resource_exhausted') ||
851
- lower.includes('quota exceeded') ||
852
- lower.includes('too many requests') ||
853
- lower.includes('requests per minute') ||
854
- lower.includes('daily limit exceeded') ||
855
- (lower.includes('limit') && lower.includes('try again')) ||
856
- lower.includes('rate limited') ||
857
- // Quota popup button text (these buttons appear when quota is hit)
858
- lower.includes('select another model') ||
859
- lower.includes('switch model') ||
860
- lower.includes('try another model') ||
861
- lower.includes('choose model');
862
-
863
- if (!hasGeminiQuota) {
864
- return {
865
- isRateLimited: false,
866
- note: text ? `No quota pattern found. UI text sample: ${text.substring(0, 300)}` : 'No readable UI text from Antigravity windows'
867
- };
868
- }
869
-
870
- const resumeAtMatch = text.match(/resume\s+(?:using\s+this\s+model\s+)?at\s+(\d{1,2}\/\d{1,2}\/\d{4})\s*,?\s*(\d{1,2}:\d{2}(?::\d{2})?)\s*(AM|PM)/i);
871
- const resumeAt = resumeAtMatch
872
- ? `${resumeAtMatch[1]}, ${resumeAtMatch[2]} ${resumeAtMatch[3].toUpperCase()}`
873
- : undefined;
874
-
875
- return {
876
- isRateLimited: true,
877
- message: text,
878
- resumeAt
879
- };
880
- } catch (error) {
881
- return {
882
- isRateLimited: false,
883
- note: `Failed to check Antigravity quota: ${error.message}`
884
- };
885
- }
114
+ async detectContinuationPrompt() {
115
+ return await this.coreManager.detectContinuationPrompt();
886
116
  }
887
117
 
888
118
  /**
889
- * Check Cursor IDE UI for quota limit messages
890
- * Detects "You've hit your usage limit" and similar quota messages
891
- * @returns {Promise<{isRateLimited: boolean, message?: string, note?: string}>}
119
+ * Click detected continuation button using AppleScript
120
+ * @returns {Promise<boolean>} True if button clicked successfully
892
121
  */
893
- async checkCursorQuotaLimit() {
894
- try {
895
- const script = `
896
- tell application "System Events"
897
- if not (exists process "Cursor") then
898
- return "NOT_RUNNING"
899
- end if
900
- tell process "Cursor"
901
- set frontmost to true
902
- delay 0.4
903
-
904
- set allText to ""
905
- try
906
- set allText to allText & (value of static text of window 1 as string) & "\\n"
907
- end try
908
-
909
- try
910
- set groupsList to groups of window 1
911
- repeat with g in groupsList
912
- try
913
- set allText to allText & (value of static text of g as string) & "\\n"
914
- end try
915
- end repeat
916
- end try
917
-
918
- -- Also check for text in UI elements like status bars, notifications, etc.
919
- try
920
- set uiElements to every UI element of window 1
921
- repeat with element in uiElements
922
- try
923
- if value of element exists then
924
- set allText to allText & (value of element as string) & "\\n"
925
- end if
926
- end try
927
- end repeat
928
- end try
929
-
930
- return allText
931
- end tell
932
- end tell
933
- `;
934
-
935
- const raw = execSync(`osascript -e '${script.replace(/'/g, "'\\\\''")}'`, {
936
- encoding: 'utf8',
937
- timeout: 8000
938
- }).trim();
939
-
940
- if (!raw || raw === 'NOT_RUNNING') {
941
- return {
942
- isRateLimited: false,
943
- note: raw === 'NOT_RUNNING' ? 'Cursor not running' : 'No readable UI text'
944
- };
945
- }
946
-
947
- const text = raw.replace(/\\s+/g, ' ').trim();
948
- const lower = text.toLowerCase();
949
-
950
- // Check for Cursor-specific quota limit messages
951
- const hasCursorQuota = lower.includes('you\'ve hit your usage limit') ||
952
- lower.includes('you have hit your usage limit') ||
953
- lower.includes('usage limit') ||
954
- lower.includes('agent usage') && lower.includes('limit') ||
955
- lower.includes('get cursor pro') ||
956
- lower.includes('upgrade to cursor pro') ||
957
- lower.includes('quota limit') ||
958
- lower.includes('rate limit') && lower.includes('cursor');
959
-
960
- if (!hasCursorQuota) {
961
- return { isRateLimited: false };
962
- }
122
+ async clickContinuationButton() {
123
+ return await this.coreManager.clickContinuationButton();
124
+ }
963
125
 
964
- return {
965
- isRateLimited: true,
966
- message: text
967
- };
968
- } catch (error) {
969
- return {
970
- isRateLimited: false,
971
- note: `Failed to check Cursor quota: ${error.message}`
972
- };
973
- }
126
+ /**
127
+ * Check Antigravity UI for Gemini quota limit popup text.
128
+ * This is best-effort: Antigravity may render popup in a WebView that AppleScript can't read.
129
+ * @returns {Promise<{isRateLimited: boolean, message?: string, resumeAt?: string, note?: string}>}
130
+ */
131
+ async checkAntigravityQuotaLimit() {
132
+ return await this.coreManager.checkAntigravityQuotaLimit();
974
133
  }
975
134
 
976
135
  /**
@@ -978,146 +137,7 @@ class AppleScriptManager {
978
137
  * @returns {Promise<{success: boolean, model?: string, error?: string}>}
979
138
  */
980
139
  async handleAntigravityQuotaLimit() {
981
- try {
982
- this.logger.log('Attempting to handle Antigravity quota limit...');
983
-
984
- const script = `
985
- tell application "System Events"
986
- tell process "Antigravity"
987
- set frontmost to true
988
- delay 0.8
989
-
990
- -- Try to find model selection button with multiple possible texts
991
- try
992
- set buttonTexts to {"Select another model", "Switch model", "Change model", "Select model", "Try another model", "Choose model"}
993
- set foundButton to missing value
994
-
995
- -- Search for button in window 1
996
- repeat with buttonText in buttonTexts
997
- try
998
- set matchingButtons to buttons of window 1 whose name contains buttonText
999
- if (count of matchingButtons) > 0 then
1000
- set foundButton to item 1 of matchingButtons
1001
- exit repeat
1002
- end if
1003
- end try
1004
- end repeat
1005
-
1006
- -- Also search in groups within window 1
1007
- if foundButton is missing value then
1008
- repeat with buttonText in buttonTexts
1009
- try
1010
- set allGroups to groups of window 1
1011
- repeat with grp in allGroups
1012
- try
1013
- set matchingButtons to buttons of grp whose name contains buttonText
1014
- if (count of matchingButtons) > 0 then
1015
- set foundButton to item 1 of matchingButtons
1016
- exit repeat
1017
- end if
1018
- end try
1019
- end repeat
1020
- if foundButton is not missing value then exit repeat
1021
- end try
1022
- end repeat
1023
- end if
1024
-
1025
- if foundButton is not missing value then
1026
- click foundButton
1027
- delay 1.5
1028
-
1029
- -- Look for model dropdown/menu items
1030
- -- Try alternative models in order of preference
1031
- set modelNames to {"Gemini 3 Flash", "Gemini 3 Pro (High)", "Gemini 3 Pro (Low)", "Claude Sonnet 4.5", "Claude Sonnet 4.5 (Thinking)", "Claude Opus 4.5 (Thinking)", "GPT-OSS 120B (Medium)"}
1032
-
1033
- repeat with modelName in modelNames
1034
- try
1035
- -- Try to find and click this model option in various locations
1036
- set modelItems to {}
1037
-
1038
- -- Try menu items
1039
- try
1040
- set modelItems to menu items of menu 1 of window 1 whose name contains modelName
1041
- end try
1042
-
1043
- -- Try buttons if no menu items found
1044
- if (count of modelItems) = 0 then
1045
- try
1046
- set modelItems to buttons of window 1 whose name contains modelName
1047
- end try
1048
- end if
1049
-
1050
- -- Search in groups
1051
- if (count of modelItems) = 0 then
1052
- try
1053
- set allGroups to groups of window 1
1054
- repeat with grp in allGroups
1055
- try
1056
- set matchingItems to (every UI element of grp whose name contains modelName)
1057
- if (count of matchingItems) > 0 then
1058
- set modelItems to matchingItems
1059
- exit repeat
1060
- end if
1061
- end try
1062
- end repeat
1063
- end try
1064
- end if
1065
-
1066
- if (count of modelItems) > 0 then
1067
- click item 1 of modelItems
1068
- delay 0.8
1069
-
1070
- -- Click "Accept all" or similar confirmation button
1071
- try
1072
- set acceptTexts to {"Accept", "OK", "Confirm", "Continue"}
1073
- repeat with acceptText in acceptTexts
1074
- try
1075
- set acceptButtons to buttons of window 1 whose name contains acceptText
1076
- if (count of acceptButtons) > 0 then
1077
- click item 1 of acceptButtons
1078
- delay 0.5
1079
- return "success:" & modelName
1080
- end if
1081
- end try
1082
- end repeat
1083
- end try
1084
-
1085
- return "success:" & modelName
1086
- end if
1087
- end try
1088
- end repeat
1089
-
1090
- return "error:no-models-available"
1091
- else
1092
- return "error:button-not-found"
1093
- end if
1094
- on error errMsg
1095
- return "error:" & errMsg
1096
- end try
1097
- end tell
1098
- end tell
1099
- `;
1100
-
1101
- const result = execSync(`osascript -e '${script.replace(/'/g, "'\\''")}'`, {
1102
- encoding: 'utf8',
1103
- timeout: 15000
1104
- }).trim();
1105
-
1106
- this.logger.log('AppleScript result:', result);
1107
-
1108
- if (result.startsWith('success:')) {
1109
- const model = result.substring(8);
1110
- this.logger.log(`Successfully switched to model: ${model}`);
1111
- return { success: true, model };
1112
- } else {
1113
- const error = result.substring(6);
1114
- this.logger.log(`Failed to switch model: ${error}`);
1115
- return { success: false, error };
1116
- }
1117
- } catch (error) {
1118
- this.logger.log('Error handling Antigravity quota limit:', error.message);
1119
- return { success: false, error: error.message };
1120
- }
140
+ return await this.coreManager.handleAntigravityQuotaLimit();
1121
141
  }
1122
142
 
1123
143
  /**
@@ -1127,38 +147,7 @@ class AppleScriptManager {
1127
147
  * @returns {Promise<Object>} Result object
1128
148
  */
1129
149
  async openIDE(ide, repoPath = null) {
1130
- const ideLower = (ide || '').toLowerCase();
1131
-
1132
- switch (ideLower) {
1133
- case 'cursor':
1134
- return await this.openCursor(repoPath);
1135
- case 'windsurf':
1136
- return await this.openWindsurf(repoPath);
1137
- case 'antigravity':
1138
- return await this.openAntigravity(repoPath);
1139
- case 'vscode':
1140
- return await this.openVSCode(repoPath);
1141
- case 'github-copilot':
1142
- // Open VS Code with GitHub Copilot extension
1143
- return await this.openVSCodeWithExtension('github-copilot', repoPath);
1144
- case 'amazon-q':
1145
- // Open VS Code with Amazon Q extension
1146
- return await this.openVSCodeWithExtension('amazon-q', repoPath);
1147
- case 'replit':
1148
- return await this.openReplit(repoPath);
1149
- case 'kiro':
1150
- return await this.openKiro(repoPath);
1151
- case 'claude':
1152
- case 'claude-code':
1153
- return await this.openClaude(repoPath);
1154
- case 'gemini':
1155
- return await this.openGemini(repoPath);
1156
- default:
1157
- return {
1158
- success: false,
1159
- error: `Unknown IDE: ${ide}. Supported: cursor, windsurf, antigravity, vscode, github-copilot, amazon-q, replit, kiro, claude, claude-code, gemini`
1160
- };
1161
- }
150
+ return await this.coreManager.openIDE(ide, repoPath);
1162
151
  }
1163
152
 
1164
153
  /**
@@ -1167,152 +156,17 @@ class AppleScriptManager {
1167
156
  * @returns {Promise<boolean>} True if safe to send text, false if in editor
1168
157
  */
1169
158
  async ensureNotInEditor(ide) {
1170
- if (ide !== 'cursor') {
1171
- return true; // Only check for Cursor
1172
- }
1173
-
1174
- try {
1175
- const safetyScript = `
1176
- tell application "System Events"
1177
- tell process "Cursor"
1178
- set frontmost to true
1179
- delay 0.5
1180
-
1181
- -- Check if we're currently in a text editor
1182
- try
1183
- -- Try to get the focused element
1184
- set focusedElement to value of attribute "AXFocused" of window 1
1185
- if focusedElement is not missing value then
1186
- -- We're in an editor, need to escape
1187
- key code 53 -- Escape key to exit editor mode
1188
- delay 0.5
1189
- return "escaped_from_editor"
1190
- else
1191
- return "not_in_editor"
1192
- end if
1193
- on error
1194
- -- If we can't determine focus, assume we're safe
1195
- return "focus_check_failed_assuming_safe"
1196
- end try
1197
- end tell
1198
- end tell
1199
- `;
1200
-
1201
- const tempFile = join(tmpdir(), `safety_check_${Date.now()}.scpt`);
1202
- try {
1203
- writeFileSync(tempFile, safetyScript, 'utf8');
1204
- const result = execSync(`osascript "${tempFile}"`, { stdio: 'pipe', encoding: 'utf8' });
1205
- this.logger.log(`🔒 [${ide}] Safety check result: ${result.trim()}`);
1206
- return true; // Always return true to proceed, but log the result
1207
- } finally {
1208
- try {
1209
- unlinkSync(tempFile);
1210
- } catch (cleanupError) {
1211
- this.logger.log(`⚠️ Failed to cleanup safety check temp file: ${cleanupError.message}`);
1212
- }
1213
- }
1214
- } catch (error) {
1215
- this.logger.log(`⚠️ Safety check failed: ${error.message}`);
1216
- return true; // Proceed anyway if safety check fails
1217
- }
159
+ // This functionality is now handled internally by the core manager
160
+ return true; // Always return true as the core manager handles safety checks
1218
161
  }
1219
162
 
1220
163
  /**
1221
- * Send text to VS Code using AppleScript
164
+ * Send text to VS Code using dedicated method
1222
165
  * @param {string} text - The text to send
1223
166
  * @returns {Promise<Object>} Result object with success status and details
1224
167
  */
1225
168
  async sendTextToVSCode(text) {
1226
- const ideName = 'VS Code';
1227
- this.logger.log(`🚀 [${ideName}] Using dedicated sendTextToVSCode method with new chat session.`);
1228
-
1229
- try {
1230
- // Get current extension from manager instance
1231
- const extension = this.currentExtension || 'gemini-code'; // Default to gemini-code
1232
- let commandText = 'Gemini Code Assist: Focus on Chat View'; // default for gemini
1233
-
1234
- if (extension === 'amazon-q') {
1235
- commandText = 'Amazon Q: Open Chat';
1236
- } else if (extension === 'github-copilot') {
1237
- commandText = 'GitHub Copilot Chat: Open Chat';
1238
- } else if (extension === 'gemini-code') {
1239
- commandText = 'Gemini Code Assist: Focus on Chat View';
1240
- } else if (extension === 'windsurf') {
1241
- commandText = 'Windsurf: Open Chat';
1242
- }
1243
-
1244
- const appleScript = `
1245
- tell application "System Events"
1246
- tell process "Visual Studio Code"
1247
- -- Check if the process is running before attempting to interact
1248
- try
1249
- set frontmost to true
1250
- delay 1.5
1251
- -- Step 1: Start new chat session first (only for Gemini)
1252
- ${extension === 'gemini-code' ? `
1253
- key code 35 using {command down, shift down}
1254
- delay 1.0
1255
- keystroke "Gemini Code Assistant New Chat"
1256
- delay 1.5
1257
- key code 36
1258
- delay 2.0
1259
- ` : ''}
1260
-
1261
- -- Step 2: Open Command Palette to focus on chat view
1262
- key code 35 using {command down, shift down}
1263
- delay 1.0
1264
-
1265
- -- Type to open the selected extension chat (increased delay for command processing)
1266
- keystroke "${commandText.replace(/"/g, '\\"')}"
1267
- delay 1.5
1268
-
1269
- -- Press Enter (increased delay for chat window to fully open)
1270
- key code 36
1271
- delay 3.0
1272
-
1273
- -- Additional step for Amazon Q: ensure it's properly focused
1274
- ${extension === 'amazon-q' ? `
1275
- -- Wait for Amazon Q to fully load and then ensure focus
1276
- delay 1.0
1277
- -- Try to set focus to the Amazon Q chat input
1278
- try
1279
- -- Look for the Amazon Q chat input field and focus it
1280
- keystroke tab
1281
- delay 0.5
1282
- keystroke tab
1283
- delay 0.5
1284
- on error
1285
- -- If tab navigation fails, continue
1286
- end try
1287
- ` : ''}
1288
-
1289
-
1290
- -- Type the message (increased delay for input field to be ready)
1291
- keystroke "${text.replace(/"/g, '\\"')}"
1292
- delay 1.0
1293
-
1294
- -- Send with Cmd+Enter or Enter
1295
- key code 36 using {command down}
1296
- delay 0.5
1297
- key code 36
1298
- delay 1.5
1299
- on error
1300
- -- Process not running or not accessible
1301
- error "Process 'Visual Studio Code' is not running or not accessible. VibeCodingMachine does not open IDEs - please open VS Code manually first."
1302
- end try
1303
- end tell
1304
- end tell
1305
- `;
1306
-
1307
- const tempFile = join(tmpdir(), `vscode_script_${Date.now()}.scpt`);
1308
- writeFileSync(tempFile, appleScript, 'utf8');
1309
- execSync(`osascript "${tempFile}"`, { stdio: 'pipe' });
1310
- unlinkSync(tempFile);
1311
-
1312
- return { success: true, method: 'applescript', message: `Message sent to ${ideName}: ${text}` };
1313
- } catch (error) {
1314
- return { success: false, error: error.message, method: 'applescript' };
1315
- }
169
+ return await this.coreManager.sendTextToVSCode(text);
1316
170
  }
1317
171
 
1318
172
  /**
@@ -1323,700 +177,25 @@ class AppleScriptManager {
1323
177
  * @returns {Promise<Object>} Result object with success status and details
1324
178
  */
1325
179
  async sendText(text, ide, repoPath = null) {
1326
- if (typeof text !== 'string') {
1327
- return {
1328
- success: false,
1329
- error: `Invalid text type: ${typeof text}. Expected string.`,
1330
- debug: { textType: typeof text, textValue: text }
1331
- };
1332
- }
1333
-
1334
- const ideName = ide === 'windsurf' ? 'Windsurf' : ide === 'cursor' ? 'Cursor' : ide === 'antigravity' ? 'Antigravity' : (ide === 'claude' || ide === 'claude-code') ? 'Claude' : ide === 'vscode' ? 'VS Code' : ide === 'kiro' ? 'AWS Kiro' : 'Unknown IDE';
1335
-
1336
- this.logger.log(`🚀 [${ideName}] Starting text send on ${this.platform} platform`);
1337
- this.logger.log(`🚀 [${ideName}] Text to send: "${text}"`);
1338
-
1339
- // Special handling for Windsurf extension within VS Code
1340
- if (ide === 'windsurf' && this.isRunningInVSCodeExtension()) {
1341
- return await this.sendTextToWindsurfExtension(text);
1342
- }
1343
-
1344
- // Handle Windows platform
1345
- if (this.platform === 'win32') {
1346
- return await this.sendTextWindows(text, ide);
1347
- }
1348
- this.logger.log(`🚀 [${ideName}] Using precise element targeting - AVOIDING TERMINAL`);
1349
-
1350
- // Safety check: Ensure we're not in an editor before sending text
1351
- await this.ensureNotInEditor(ide);
1352
-
1353
- // Enhanced diagnostic for Cursor to understand UI structure and prevent file typing
1354
- if (ide === 'cursor') {
1355
- try {
1356
- const diagnosticScript = `
1357
- tell application "System Events"
1358
- tell process "Cursor"
1359
- set frontmost to true
1360
- delay 1
1361
-
1362
- -- Check if we're currently in an editor text area
1363
- set inEditor to false
1364
- try
1365
- set activeElement to value of attribute "AXFocused" of window 1
1366
- if activeElement is not missing value then
1367
- set inEditor to true
1368
- end if
1369
- on error
1370
- -- Continue
1371
- end try
1372
-
1373
- -- Log all text fields and their descriptions
1374
- set allTextFields to text field of window 1
1375
- set fieldCount to count of allTextFields
1376
- log "Found " & fieldCount & " text fields in Cursor"
1377
-
1378
- repeat with i from 1 to fieldCount
1379
- try
1380
- set currentField to item i of allTextFields
1381
- set fieldDesc to description of currentField
1382
- log "Field " & i & ": " & fieldDesc
1383
- on error
1384
- log "Field " & i & ": No description available"
1385
- end try
1386
- end repeat
1387
-
1388
- -- Log all groups and their structure
1389
- set allGroups to group of window 1
1390
- set groupCount to count of allGroups
1391
- log "Found " & groupCount & " groups in Cursor"
1392
-
1393
- -- Log current focus state
1394
- log "Currently in editor: " & inEditor
1395
- end tell
1396
- end tell
1397
- `;
1398
-
1399
- this.logger.log(`🔍 [${ideName}] Running enhanced diagnostic script to understand Cursor UI structure...`);
1400
-
1401
- // Use file-based AppleScript execution to avoid shell escaping issues
1402
- const tempFile = join(tmpdir(), `cursor_diagnostic_${Date.now()}.scpt`);
1403
- try {
1404
- writeFileSync(tempFile, diagnosticScript, 'utf8');
1405
- const diagnosticResult = execSync(`osascript "${tempFile}"`, { stdio: 'pipe', encoding: 'utf8' });
1406
- this.logger.log(`✅ [${ideName}] Diagnostic completed: ${diagnosticResult.trim()}`);
1407
- } finally {
1408
- // Clean up temporary file
1409
- try {
1410
- unlinkSync(tempFile);
1411
- } catch (cleanupError) {
1412
- this.logger.log(`⚠️ [${ideName}] Failed to cleanup diagnostic temp file: ${cleanupError.message}`);
1413
- }
1414
- }
1415
- } catch (diagnosticError) {
1416
- this.logger.log(`⚠️ [${ideName}] Diagnostic failed: ${diagnosticError.message}`);
1417
- }
1418
- }
1419
-
1420
- try {
1421
- let appleScript;
1422
-
1423
- if (ide === 'windsurf') {
1424
- // Windsurf: activate the existing instance, then use Cmd+Shift+I to open a
1425
- // fresh Cascade conversation — this reliably focuses the chat input without
1426
- // restarting the IDE.
1427
- this.logger.log('🔄 [Windsurf] Opening new Cascade conversation with Cmd+Shift+I...');
1428
- // Windsurf is spawned detached so vcm exits first and the terminal
1429
- // goes idle before any keystrokes fire (prevents paste landing in terminal)
1430
- appleScript = `
1431
- -- Wait for the calling terminal process to exit and go idle
1432
- delay 3
1433
-
1434
- tell application "Windsurf"
1435
- activate
1436
- end tell
1437
- delay 1.0
1438
-
1439
- -- Target Windsurf by bundle ID (both Windsurf and VS Code run as "Electron")
1440
- tell application "System Events"
1441
- set windsurfProc to first process whose bundle identifier is "com.exafunction.windsurf"
1442
- tell windsurfProc
1443
- set frontmost to true
1444
- delay 0.5
1445
-
1446
- -- Escape: defocus terminal input
1447
- key code 53
1448
- delay 0.5
1449
-
1450
- -- Cmd+Shift+L: focus Cascade chat input
1451
- keystroke "l" using {command down, shift down}
1452
- delay 2.0
1453
-
1454
- -- Clear any auto-inserted context (e.g. @terminal:zsh)
1455
- keystroke "a" using {command down}
1456
- delay 0.3
1457
- key code 51
1458
- delay 0.3
1459
-
1460
- -- Paste message
1461
- set the clipboard to "${this.escapeAppleScriptString(text)}"
1462
- keystroke "v" using {command down}
1463
- delay 0.5
1464
-
1465
- -- Submit
1466
- key code 36
1467
- delay 1.0
1468
- end tell
1469
- end tell
1470
- `;
1471
- } else if (ide === 'claude' || ide === 'claude-code') {
1472
- // Use a different approach for Claude - find existing Claude terminal and send text
1473
- const targetRepoPath = repoPath || '/Users/jesse/code/mediawink/vibecodingmachine';
1474
- this.logger.log(`🔍 [Claude] Using repo path for terminal detection: "${targetRepoPath}" (passed: "${repoPath}")`);
1475
-
1476
- // Check if Claude process is running
1477
- let claudeProcessCount = 0;
1478
- try {
1479
- const processCheck = execSync('ps aux | grep -i "claude" | grep -v grep | grep -v "Claude.app" | wc -l', { encoding: 'utf8' }).trim();
1480
- claudeProcessCount = parseInt(processCheck);
1481
- this.logger.log(`🔍 [Claude] Found ${claudeProcessCount} Claude processes`);
1482
- } catch (error) {
1483
- this.logger.log(`⚠️ [Claude] Process check failed: ${error.message}`);
1484
- }
1485
-
1486
- if (claudeProcessCount === 0) {
1487
- // No Claude process running - try to open it
1488
- this.logger.log('⚠️ [Claude] No Claude process found, attempting to open Claude...');
1489
- const openResult = await this.openClaude(repoPath);
1490
-
1491
- if (!openResult.success) {
1492
- return {
1493
- success: false,
1494
- method: 'applescript',
1495
- error: `No Claude process found and failed to open Claude: ${openResult.error}`,
1496
- note: 'Attempted to open Claude but it failed'
1497
- };
1498
- }
1499
-
1500
- // Wait for Claude to fully start
1501
- this.logger.log('✅ [Claude] Claude opened successfully, waiting for it to be ready...');
1502
- await new Promise(resolve => setTimeout(resolve, 5000));
1503
-
1504
- // Re-check if Claude process is now running
1505
- try {
1506
- const recheckProcess = execSync('ps aux | grep -i "claude" | grep -v grep | grep -v "Claude.app" | wc -l', { encoding: 'utf8' }).trim();
1507
- const recheckCount = parseInt(recheckProcess);
1508
- if (recheckCount === 0) {
1509
- return {
1510
- success: false,
1511
- method: 'applescript',
1512
- error: 'Claude was opened but process not detected after waiting',
1513
- note: 'Claude may need more time to start'
1514
- };
1515
- }
1516
- this.logger.log(`✅ [Claude] Confirmed ${recheckCount} Claude processes running after opening`);
1517
- } catch (recheckError) {
1518
- this.logger.log(`⚠️ [Claude] Could not verify Claude process after opening: ${recheckError.message}`);
1519
- }
1520
- }
1521
-
1522
- // Claude process exists - find and send text to the specific Claude terminal window
1523
- this.logger.log('🔄 [Claude] Finding specific Claude terminal window...');
1524
-
1525
- // CRITICAL: Validate text parameter before using in template string
1526
- if (!text || typeof text !== 'string') {
1527
- this.logger.error(`❌ [Claude] Invalid text parameter: type=${typeof text}, value=${text}`);
1528
- return {
1529
- success: false,
1530
- method: 'applescript',
1531
- error: `Invalid text parameter for Claude: received ${typeof text} instead of string`,
1532
- note: 'Text validation failed'
1533
- };
1534
- }
1535
-
1536
- const findAndSendToClaudeScript = `
1537
- tell application "Terminal"
1538
- activate
1539
- set claudeWindow to null
1540
- set claudeTab to null
1541
- set windowCount to count of windows
1542
-
1543
- -- Search through all terminal windows to find the one running Claude IN THE CORRECT DIRECTORY
1544
- repeat with i from 1 to windowCount
1545
- try
1546
- set currentWindow to window i
1547
- set currentTab to selected tab of currentWindow
1548
- set historyText to history of currentTab as text
1549
-
1550
- -- Check for Claude AND the correct repo path to avoid sending to wrong terminal
1551
- -- CRITICAL FIX: Must check BOTH Claude process AND repo path to avoid wrong terminal
1552
- -- Also check that Claude is CURRENTLY RUNNING (not just in history)
1553
- set hasClaudeCode to historyText contains "Claude Code" or historyText contains "claude --dangerously-skip-permissions"
1554
- set hasRepoPath to historyText contains "${escapedRepoPath}"
1555
- -- Check for Claude interactive prompts or working state
1556
- set hasClaudePrompt to historyText contains "claude>" or historyText contains "What can I help you build" or historyText contains "bypass permissions"
1557
-
1558
- if hasClaudeCode and hasRepoPath and hasClaudePrompt then
1559
- set claudeWindow to currentWindow
1560
- set claudeTab to currentTab
1561
- exit repeat
1562
- end if
1563
- on error
1564
- -- Continue to next window
1565
- end try
1566
- end repeat
1567
-
1568
- if claudeWindow is not null then
1569
- -- Found existing Claude terminal, bring it to front
1570
- set index of claudeWindow to 1
1571
- delay 1.0
1572
- else
1573
- error "No Claude terminal found in directory: ${escapedRepoPath}"
1574
- end if
1575
- end tell
1576
-
1577
- -- CRITICAL: Must use System Events, NOT 'do script'
1578
- -- REASON: 'do script' only submits when STARTING a command, not when process is already running
1579
- -- This was the root cause of "Start Auto" not submitting text to Claude (Oct 17, 2025)
1580
- -- DO NOT CHANGE THIS TO 'do script' - it will break text submission!
1581
- tell application "System Events"
1582
- tell process "Terminal"
1583
- set frontmost to true
1584
- delay 0.5
1585
-
1586
- -- Type the text
1587
- keystroke "${text.replace(/"/g, '\\"')}"
1588
- delay 1.0
1589
-
1590
- -- Submit with return (REQUIRED - 'do script' won't do this when Claude is running)
1591
- keystroke return
1592
- delay 0.3
1593
- end tell
1594
- end tell
1595
- `;
1596
-
1597
- // Execute the AppleScript using file-based approach
1598
- try {
1599
- const tempFile = join(tmpdir(), `claude_script_${Date.now()}.scpt`);
1600
- writeFileSync(tempFile, findAndSendScript, 'utf8');
1601
-
1602
- let result;
1603
- try {
1604
- result = execSync(`osascript "${tempFile}"`, {
1605
- encoding: 'utf8',
1606
- timeout: 30000, // 30 seconds to allow for window searching and delays
1607
- stdio: 'pipe'
1608
- }).toString().trim();
1609
- } finally {
1610
- // Clean up temp file
1611
- try { unlinkSync(tempFile); } catch (cleanupError) { this.logger.log(`⚠️ [Claude] Failed to cleanup temp file: ${cleanupError.message}`); }
1612
- }
1613
-
1614
- this.logger.log(`✅ [Claude] AppleScript execution output:`, result);
1615
-
1616
- // Verify that the sent text appears in the returned history snippet
1617
- try {
1618
- const sentVerified = result && result.indexOf('|HIST:') !== -1 && result.split('|HIST:')[1].includes(`${text}`);
1619
- if (sentVerified) {
1620
- this.logger.log('✅ [Claude] Verified sent text present in terminal history snippet');
1621
- return {
1622
- success: true,
1623
- method: 'applescript',
1624
- message: `Text sent and verified in Claude terminal: ${text}`,
1625
- note: result
1626
- };
1627
- }
1628
- this.logger.log('⚠️ [Claude] Sent text NOT found in terminal history snippet; will attempt deterministic retry');
1629
- } catch (verifyErr) {
1630
- this.logger.log('⚠️ [Claude] Verification parse error:', verifyErr.message || verifyErr);
1631
- }
1632
-
1633
- // If verification failed, attempt a deterministic direct send to window 1 tab 1
1634
- try {
1635
- const det = await this.sendDeterministicToTerminalWindow1(text);
1636
- if (det && det.success) {
1637
- this.logger.log('✅ [Claude] Deterministic retry verified and succeeded');
1638
- return { success: true, method: 'applescript', message: `Text sent to newly opened Claude terminal: ${text}`, note: det.note };
1639
- }
1640
- this.logger.log('⚠️ [Claude] Deterministic retry did not verify:', det && det.note ? det.note : det && det.error ? det.error : det);
1641
- } catch (detErr) {
1642
- this.logger.log('❌ [Claude] Deterministic retry failed:', detErr.message || detErr);
1643
- }
1644
-
1645
- // CRITICAL FIX: If we reach here, text was sent but not verified
1646
- // Return success anyway since the AppleScript execution succeeded
1647
- this.logger.log('⚠️ [Claude] Text sent but verification inconclusive, returning success');
1648
- return {
1649
- success: true,
1650
- method: 'applescript',
1651
- message: `Text sent to Claude terminal (verification inconclusive): ${text}`,
1652
- note: 'Text sent successfully but verification did not confirm'
1653
- };
1654
- } catch (error) {
1655
- this.logger.log('❌ [Claude] AppleScript failed:', error.message);
1656
- return {
1657
- success: false,
1658
- method: 'applescript',
1659
- error: `Failed to send text to Claude: ${error.message}`,
1660
- note: 'Claude AppleScript automation failed'
1661
- };
1662
- }
1663
- } else if (ide === 'cursor') {
1664
- // Cursor already has new chat logic in generateAIPanelFocusScript (Cmd+T)
1665
- appleScript = this.appleScriptUtils.generateAIPanelFocusScript(text, 'Cursor');
1666
- } else if (ide === 'vscode' || ide === 'github-copilot' || ide === 'amazon-q') {
1667
- // Delegate to the dedicated VS Code method
1668
- // github-copilot and amazon-q are VS Code extensions
1669
- return await this.sendTextToVSCode(text);
1670
- } else if (ide === 'antigravity') {
1671
- // AppleScript for Google Antigravity - Cmd+Shift+L opens Agent chat (non-toggle)
1672
- this.logger.log('🔄 [Antigravity] Sending text to Antigravity Agent chat...');
1673
- appleScript = `
1674
- tell application "System Events"
1675
- tell process "Antigravity"
1676
- -- Check if the process is running before attempting to interact
1677
- try
1678
- set frontmost to true
1679
- delay 1
1680
-
1681
- -- Open Agent chat with Cmd+Shift+L (does not toggle)
1682
- key code 37 using {command down, shift down}
1683
- delay 1.5
1684
-
1685
- -- Type the message
1686
- keystroke "${text.replace(/"/g, '\\"')}"
1687
- delay 0.5
1688
-
1689
- -- Send with Enter
1690
- key code 36
1691
- delay 0.5
1692
-
1693
- return "Message sent to Antigravity Agent"
1694
- on error
1695
- -- Process not running or not accessible
1696
- error "Process 'Antigravity' is not running or not accessible. VibeCodingMachine does not open IDEs - please open Antigravity manually first."
1697
- end try
1698
- end tell
1699
- end tell
1700
- `;
1701
- } else if (ide === 'kiro') {
1702
- // AWS Kiro support
1703
- this.logger.log('🚀 [AWS Kiro] Sending text via AppleScript accessibility (with fallback check)...');
1704
-
1705
- appleScript = `
1706
- tell application "System Events"
1707
- set processName to "AWS Kiro"
1708
- try
1709
- if not (exists process "AWS Kiro") then
1710
- set processName to "Kiro"
1711
- end if
1712
- on error
1713
- set processName to "Kiro"
1714
- end try
1715
-
1716
- tell process processName
1717
- set frontmost to true
1718
- delay 1.0
1719
-
1720
- -- Attempt to focus chat via standard AI IDE shortcuts
1721
- -- Try Cmd+L (Cursor/Windsurf standard)
1722
- try
1723
- key code 37 using {command down}
1724
- delay 0.5
1725
- on error
1726
- -- Ignore if fails
1727
- end try
1728
-
1729
- -- Use clipboard for more reliable text entry than keystrokes
1730
- set the clipboard to "${text.replace(/"/g, '\\"')}"
1731
- delay 0.3
1732
-
1733
- -- Paste
1734
- key code 9 using {command down}
1735
- delay 0.5
1736
-
1737
- -- Send Enter
1738
- key code 36
1739
- delay 0.5
1740
- end tell
1741
- end tell
1742
- `;
1743
- } else {
1744
- return {
1745
- success: false,
1746
- error: `Unsupported IDE for AppleScript: ${ide}`,
1747
- note: 'AppleScript is only supported for Cursor, Windsurf, Antigravity, VS Code, GitHub Copilot, Amazon Q, AWS Kiro, and Claude'
1748
- };
1749
- }
1750
-
1751
- this.logger.log(`🚀 [${ideName}] Executing AppleScript with PRECISE CHAT TARGETING...`);
1752
-
1753
- // Guard against undefined appleScript (can happen with Claude path)
1754
- if (!appleScript) {
1755
- this.logger.error(`❌ [${ideName}] AppleScript variable is undefined - invalid code path`);
1756
- return {
1757
- success: false,
1758
- method: 'applescript',
1759
- error: 'AppleScript not generated - this should not happen for non-Claude IDEs',
1760
- note: 'Check IDE-specific code path logic'
1761
- };
1762
- }
1763
-
1764
- // FIXED: Use file-based AppleScript execution to avoid shell escaping issues
1765
- const tempFile = join(tmpdir(), `applescript_${Date.now()}.scpt`);
1766
- writeFileSync(tempFile, appleScript, 'utf8');
1767
-
1768
- // Windsurf: use different execution strategies based on context
1769
- // The AppleScript has a leading `delay 3` to let the terminal go idle first.
1770
- if (ide === 'windsurf') {
1771
- // Check if running in Electron app context
1772
- const isElectronContext = process.versions && process.versions.electron;
1773
-
1774
- if (isElectronContext) {
1775
- // In Electron app, use synchronous execution to avoid timing issues
1776
- this.logger.log(`🔧 [${ideName}] Running in Electron context - using synchronous AppleScript execution`);
1777
- try {
1778
- const out = execSync(`osascript "${tempFile}"`, {
1779
- encoding: 'utf8',
1780
- timeout: 30000,
1781
- stdio: 'pipe'
1782
- }).trim();
1783
-
1784
- try { unlinkSync(tempFile); } catch (_) {}
1785
-
1786
- this.logger.log(`✅ [${ideName}] Synchronous AppleScript completed:`, out);
1787
- return {
1788
- success: true,
1789
- method: 'applescript',
1790
- message: `Message sent to ${ideName}: ${text}`,
1791
- note: 'Message sent via synchronous AppleScript execution'
1792
- };
1793
- } catch (syncError) {
1794
- this.logger.error(`❌ [${ideName}] Synchronous AppleScript failed:`, syncError.message);
1795
- try { unlinkSync(tempFile); } catch (_) {}
1796
- return {
1797
- success: false,
1798
- method: 'applescript',
1799
- error: `Synchronous AppleScript execution failed: ${syncError.message}`
1800
- };
1801
- }
1802
- } else {
1803
- // CLI context: spawn detached so vcm exits before keystrokes fire
1804
- this.logger.log(`🔧 [${ideName}] Running in CLI context - using detached AppleScript execution`);
1805
- const child = spawn('osascript', [tempFile], {
1806
- detached: true,
1807
- stdio: 'ignore',
1808
- });
1809
- child.on('exit', () => {
1810
- try { unlinkSync(tempFile); } catch (_) {}
1811
- });
1812
- child.unref();
1813
- this.logger.log(`✅ [${ideName}] AppleScript launched detached - will fire after terminal goes idle`);
1814
- return {
1815
- success: true,
1816
- method: 'applescript',
1817
- message: `Message sent to ${ideName}: ${text}`,
1818
- note: 'AppleScript running detached; message will appear in Cascade in ~3s'
1819
- };
1820
- }
1821
- }
1822
-
1823
- try {
1824
- execSync(`osascript "${tempFile}"`, { stdio: 'pipe' });
1825
- this.logger.log(`✅ [${ideName}] AppleScript executed successfully - PRECISE TARGETING`);
1826
- this.logger.log(`✅ [${ideName}] Message sent successfully via precise element targeting`);
1827
- return {
1828
- success: true,
1829
- method: 'applescript',
1830
- message: `Message sent to ${ideName}: ${text}`,
1831
- note: 'Message sent via AppleScript automation'
1832
- };
1833
- } finally {
1834
- // Clean up temporary file
1835
- try {
1836
- unlinkSync(tempFile);
1837
- } catch (cleanupError) {
1838
- this.logger.log(`⚠️ [${ideName}] Failed to cleanup temp file: ${cleanupError.message}`);
1839
- }
1840
- }
1841
-
1842
- } catch (error) {
1843
- this.logger.log('AppleScript interaction failed:', error.message);
1844
-
1845
- // Check for Accessibility permission error
1846
- if (error.message.includes('not allowed to send keystrokes') ||
1847
- error.message.includes('assistive devices') ||
1848
- (error.message.includes('System Events') && error.message.includes('not allowed'))) {
1849
-
1850
- this.logger.log('⚠️ Accessibility permissions missing. Opening System Settings...');
1851
- try {
1852
- // Open System Settings -> Privacy & Security -> Accessibility
1853
- execSync('open "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"');
1854
- } catch (settingsErr) {
1855
- this.logger.log('Failed to open System Settings:', settingsErr.message);
1856
- }
1857
-
1858
- return {
1859
- success: false,
1860
- error: 'Missing Accessibility Permissions',
1861
- message: 'Please grant Accessibility permissions to Terminal/Node in System Settings',
1862
- permissionError: true,
1863
- note: 'macOS blocked keystroke automation. Opened settings for user.'
1864
- };
1865
- }
1866
-
1867
- // For Claude, don't fall back to simulated response - return actual failure
1868
- if (ide === 'claude' || ide === 'claude-code') {
1869
- return {
1870
- success: false,
1871
- method: 'applescript',
1872
- error: `Failed to send text to Claude: ${error.message}`,
1873
- note: 'Claude AppleScript automation failed. Check if Claude is running and accessible.'
1874
- };
1875
- }
1876
-
1877
- // For Antigravity, also return failure to trigger user notification in auto.js
1878
- if (ide === 'antigravity' || ide === 'kiro') {
1879
- return {
1880
- success: false,
1881
- method: 'applescript',
1882
- error: `Failed to send text to ${ideName}: ${error.message}`,
1883
- note: `${ideName} automation failed.`
1884
- };
1885
- }
180
+ return await this.coreManager.sendText(text, ide, repoPath);
181
+ }
1886
182
 
1887
- // For other IDEs, fall back to simulated response
1888
- return {
1889
- success: true,
1890
- method: 'simulated',
1891
- message: `Simulated ${ideName} response: ${text}`,
1892
- note: `${ideName} AppleScript automation failed. Using simulated response for testing.`
1893
- };
1894
- }
183
+ /**
184
+ * Read chat response from IDE using AppleScript
185
+ * @param {string} ide - The IDE name ('cursor', 'windsurf', 'vscode')
186
+ * @returns {Promise<string>} The chat response text
187
+ */
188
+ async readChatResponse(ide) {
189
+ return await this.coreManager.readChatResponse(ide);
1895
190
  }
1896
191
 
1897
192
  /**
1898
193
  * Check Claude terminal output for session limit errors
1899
- * Reads the terminal history and detects if Claude has hit its usage limit
194
+ * Reads terminal history and detects if Claude has hit its usage limit
1900
195
  * @returns {Promise<Object>} Result object with sessionLimitReached flag and details
1901
196
  */
1902
197
  async checkClaudeSessionLimit() {
1903
- try {
1904
- this.logger.log('🔍 Checking Claude terminal for session limit errors...');
1905
-
1906
- const checkScript = `
1907
- tell application "Terminal"
1908
- set claudeWindow to null
1909
- set claudeTab to null
1910
- set windowCount to count of windows
1911
-
1912
- -- Search through all terminal windows to find the one running Claude
1913
- repeat with i from 1 to windowCount
1914
- try
1915
- set currentWindow to window i
1916
- set currentTab to selected tab of currentWindow
1917
- set historyText to history of currentTab as text
1918
-
1919
- -- Check if this is the Claude terminal
1920
- if historyText contains "Claude Code" or historyText contains "claude --dangerously-skip-permissions" then
1921
- set claudeWindow to currentWindow
1922
- set claudeTab to currentTab
1923
- exit repeat
1924
- end if
1925
- on error
1926
- -- Continue to next window
1927
- end try
1928
- end repeat
1929
-
1930
- if claudeWindow is not null then
1931
- -- Get the last ~500 characters of history to check for recent errors
1932
- set fullHistory to history of claudeTab as text
1933
- set historyLength to length of fullHistory
1934
- set recentHistory to ""
1935
-
1936
- if historyLength > 500 then
1937
- set recentHistory to text -500 thru -1 of fullHistory
1938
- else
1939
- set recentHistory to fullHistory
1940
- end if
1941
-
1942
- return recentHistory
1943
- else
1944
- error "No Claude terminal found"
1945
- end if
1946
- end tell
1947
- `;
1948
-
1949
- const tempFile = join(tmpdir(), `check_claude_limit_${Date.now()}.scpt`);
1950
- writeFileSync(tempFile, checkScript, 'utf8');
1951
-
1952
- const recentOutput = execSync(`osascript "${tempFile}"`, {
1953
- encoding: 'utf8',
1954
- timeout: 10000,
1955
- stdio: 'pipe'
1956
- }).trim();
1957
-
1958
- // Clean up temp file
1959
- try {
1960
- unlinkSync(tempFile);
1961
- } catch (cleanupError) {
1962
- this.logger.log(`⚠️ Failed to cleanup temp file: ${cleanupError.message}`);
1963
- }
1964
-
1965
- // Check for session limit patterns
1966
- const sessionLimitPatterns = [
1967
- 'Session limit reached',
1968
- 'session limit reached',
1969
- 'usage limit',
1970
- '/upgrade to increase your usage limit',
1971
- 'resets at',
1972
- /resets \d{1,2}(am|pm)/i // Matches "resets 7pm", "resets 10am", etc.
1973
- ];
1974
-
1975
- const sessionLimitReached = sessionLimitPatterns.some(pattern => {
1976
- if (pattern instanceof RegExp) {
1977
- return pattern.test(recentOutput);
1978
- }
1979
- return recentOutput.toLowerCase().includes(pattern.toLowerCase());
1980
- });
1981
-
1982
- if (sessionLimitReached) {
1983
- this.logger.log('🚨 SESSION LIMIT DETECTED in Claude terminal output');
1984
- this.logger.log('📋 Recent output:', recentOutput.substring(Math.max(0, recentOutput.length - 200)));
1985
-
1986
- // Extract reset time if present (e.g., "resets 10am", "resets 7pm")
1987
- const resetTimeMatch = recentOutput.match(/resets\s+(\d{1,2})(am|pm)/i);
1988
- let resetTime = null;
1989
- if (resetTimeMatch) {
1990
- resetTime = `${resetTimeMatch[1]}${resetTimeMatch[2].toLowerCase()}`;
1991
- this.logger.log(`🕐 Reset time detected: ${resetTime}`);
1992
- }
1993
-
1994
- return {
1995
- sessionLimitReached: true,
1996
- message: 'Claude session limit has been reached',
1997
- resetTime: resetTime, // Add reset time to response
1998
- recentOutput: recentOutput,
1999
- note: resetTime
2000
- ? `Claude usage limit exceeded - resets at ${resetTime}. Please wait or upgrade.`
2001
- : 'Claude usage limit exceeded - please wait for reset or upgrade'
2002
- };
2003
- } else {
2004
- this.logger.log('✅ No session limit detected');
2005
- return {
2006
- sessionLimitReached: false,
2007
- message: 'No session limit detected',
2008
- recentOutput: recentOutput
2009
- };
2010
- }
2011
-
2012
- } catch (error) {
2013
- this.logger.log('❌ Error checking Claude session limit:', error.message);
2014
- return {
2015
- sessionLimitReached: false,
2016
- error: error.message,
2017
- message: 'Failed to check session limit'
2018
- };
2019
- }
198
+ return await this.coreManager.checkClaudeSessionLimit();
2020
199
  }
2021
200
 
2022
201
  /**
@@ -2026,1003 +205,17 @@ class AppleScriptManager {
2026
205
  * @returns {Promise<Object>} Result object with success status and details
2027
206
  */
2028
207
  async sendTextWindows(text, ide) {
2029
- if (!this.windowsManager) {
2030
- return {
2031
- success: false,
2032
- error: 'Windows automation manager not available',
2033
- method: 'windows-automation'
2034
- };
2035
- }
2036
-
2037
- try {
2038
- this.logger.log(`🔧 Windows: Sending text to ${ide}...`);
2039
-
2040
- switch (ide.toLowerCase()) {
2041
- case 'cursor':
2042
- // Try the native Windows API approach first, then fallback to basic PowerShell
2043
- let result = await this.windowsManager.sendTextToCursorNative(text);
2044
- if (!result.success) {
2045
- this.logger.log('🔧 Windows: Native approach failed, trying basic PowerShell...');
2046
- result = await this.windowsManager.sendTextToCursor(text);
2047
- }
2048
- return result;
2049
-
2050
- case 'vscode':
2051
- case 'windsurf':
2052
- // For now, use the same approach as Cursor for other IDEs
2053
- return await this.windowsManager.sendTextToCursor(text);
2054
-
2055
- default:
2056
- return {
2057
- success: false,
2058
- error: `Windows automation not implemented for IDE: ${ide}`,
2059
- method: 'windows-automation'
2060
- };
2061
- }
2062
- } catch (error) {
2063
- this.logger.error(`❌ Windows: Error sending text to ${ide}:`, error.message);
2064
- return {
2065
- success: false,
2066
- error: `Windows automation error: ${error.message}`,
2067
- method: 'windows-automation'
2068
- };
2069
- }
2070
- }
2071
-
2072
- /**
2073
- * Read chat response from IDE using AppleScript
2074
- * @param {string} ide - The IDE name ('cursor' or 'windsurf')
2075
- * @returns {Promise<string>} The chat response text
2076
- */
2077
- async readChatResponse(ide) {
2078
- if (ide !== 'windsurf' && ide !== 'cursor' && ide !== 'vscode') {
2079
- return 'Error: AppleScript reading is only supported for Cursor, Windsurf, and VS Code';
2080
- }
2081
-
2082
- const ideName = ide === 'windsurf' ? 'Windsurf' : ide === 'cursor' ? 'Cursor' : ide === 'vscode' ? 'VS Code' : 'Unknown';
2083
- this.logger.log(`${ideName} detected - using AppleScript to read chat response`);
2084
-
2085
- try {
2086
- // For Cursor, try CDP first if available
2087
- if (ide === 'cursor') {
2088
- try {
2089
- this.logger.log('🔧 Attempting CDP method for Cursor...');
2090
- const cdpResponse = await this.readCursorResponseViaCDP();
2091
- if (cdpResponse && cdpResponse.length > 20) {
2092
- this.logger.log('✅ Successfully read Cursor response via CDP');
2093
- return cdpResponse;
2094
- }
2095
- } catch (error) {
2096
- this.logger.log('⚠️ CDP method failed, falling back to AppleScript:', error.message);
2097
- }
2098
- }
2099
-
2100
- // Try clipboard-based response detection
2101
- if (ide === 'cursor') {
2102
- try {
2103
- this.logger.log('🔧 Attempting clipboard-based response detection...');
2104
- const clipboardResponse = await this.readCursorResponseViaClipboard();
2105
- if (clipboardResponse && clipboardResponse.length > 20) {
2106
- this.logger.log('✅ Successfully read Cursor response via clipboard');
2107
- return clipboardResponse;
2108
- }
2109
- } catch (error) {
2110
- this.logger.log('⚠️ Clipboard method failed, falling back to AppleScript:', error.message);
2111
- }
2112
- }
2113
-
2114
- let appleScript;
2115
-
2116
- if (ide === 'cursor') {
2117
- // Enhanced AppleScript to read the chat response from Cursor
2118
- appleScript = `
2119
- tell application "System Events"
2120
- tell process "Cursor"
2121
- set frontmost to true
2122
- delay 1
2123
-
2124
- -- Method 1: Try to get text from web view content (most likely location)
2125
- try
2126
- set webViewGroups to group of window 1
2127
- set responseText to ""
2128
- repeat with grp in webViewGroups
2129
- try
2130
- -- Look for text areas within groups (web view content)
2131
- set textAreas to text area of grp
2132
- repeat with txtArea in textAreas
2133
- try
2134
- set textValue to value of txtArea
2135
- if textValue is not "" and textValue is not missing value then
2136
- set responseText to responseText & textValue & "\\n"
2137
- end if
2138
- on error
2139
- -- Continue to next text area
2140
- end try
2141
- end repeat
2142
-
2143
- -- Look for static text within groups
2144
- set staticTexts to static text of grp
2145
- repeat with txt in staticTexts
2146
- try
2147
- set textValue to value of txt
2148
- if textValue is not "" and textValue is not missing value then
2149
- set responseText to responseText & textValue & "\\n"
2150
- end if
2151
- on error
2152
- -- Continue to next static text
2153
- end try
2154
- end repeat
2155
- on error
2156
- -- Continue to next group
2157
- end try
2158
- end repeat
2159
- if responseText is not "" then
2160
- return responseText
2161
- end if
2162
- on error
2163
- -- Continue to next method
2164
- end try
2165
-
2166
- -- Method 2: Try to get text from all text areas in the window
2167
- try
2168
- set allTextAreas to text area of window 1
2169
- set responseText to ""
2170
- repeat with txtArea in allTextAreas
2171
- try
2172
- set textValue to value of txtArea
2173
- if textValue is not "" and textValue is not missing value then
2174
- set responseText to responseText & textValue & "\\n"
2175
- end if
2176
- on error
2177
- -- Continue to next text area
2178
- end try
2179
- end repeat
2180
- if responseText is not "" then
2181
- return responseText
2182
- end if
2183
- on error
2184
- -- Continue to next method
2185
- end try
2186
-
2187
- -- Method 3: Try to get text from all static text elements
2188
- try
2189
- set allTexts to static text of window 1
2190
- set responseText to ""
2191
- repeat with txt in allTexts
2192
- try
2193
- set textValue to value of txt
2194
- if textValue is not "" and textValue is not missing value then
2195
- set responseText to responseText & textValue & "\\n"
2196
- end if
2197
- on error
2198
- -- Continue to next text element
2199
- end try
2200
- end repeat
2201
- if responseText is not "" then
2202
- return responseText
2203
- end if
2204
- on error
2205
- -- Continue to next method
2206
- end try
2207
-
2208
- -- Method 4: Try clipboard approach (select all and copy)
2209
- try
2210
- key code 0 using {command down}
2211
- delay 0.5
2212
- key code 8 using {command down}
2213
- delay 0.5
2214
- set clipboardText to (the clipboard)
2215
- if clipboardText is not "" then
2216
- return clipboardText
2217
- end if
2218
- on error
2219
- -- Continue to next method
2220
- end try
2221
-
2222
- -- Method 5: Try to find scroll areas and get their content
2223
- try
2224
- set scrollAreas to scroll area of window 1
2225
- set responseText to ""
2226
- repeat with scrollArea in scrollAreas
2227
- try
2228
- set scrollTexts to static text of scrollArea
2229
- repeat with txt in scrollTexts
2230
- try
2231
- set textValue to value of txt
2232
- if textValue is not "" and textValue is not missing value then
2233
- set responseText to responseText & textValue & "\\n"
2234
- end if
2235
- on error
2236
- -- Continue to next text
2237
- end try
2238
- end repeat
2239
- on error
2240
- -- Continue to next scroll area
2241
- end try
2242
- end repeat
2243
- if responseText is not "" then
2244
- return responseText
2245
- end if
2246
- on error
2247
- -- Continue to next method
2248
- end try
2249
-
2250
- -- Method 6: Try to access web view content through accessibility
2251
- try
2252
- set webViews to group of window 1
2253
- set responseText to ""
2254
- repeat with webView in webViews
2255
- try
2256
- -- Try to get all accessible elements within the web view
2257
- set accessibleElements to every UI element of webView
2258
- repeat with element in accessibleElements
2259
- try
2260
- set elementValue to value of element
2261
- if elementValue is not "" and elementValue is not missing value then
2262
- set responseText to responseText & elementValue & "\\n"
2263
- end if
2264
- on error
2265
- -- Continue to next element
2266
- end try
2267
- end repeat
2268
- on error
2269
- -- Continue to next web view
2270
- end try
2271
- end repeat
2272
- if responseText is not "" then
2273
- return responseText
2274
- end if
2275
- on error
2276
- -- Continue to next method
2277
- end try
2278
-
2279
- -- Method 7: Try to use JavaScript injection (if possible)
2280
- try
2281
- -- This is a fallback method that might work in some cases
2282
- set responseText to ""
2283
- set allElements to every UI element of window 1
2284
- repeat with element in allElements
2285
- try
2286
- set elementValue to value of element
2287
- if elementValue is not "" and elementValue is not missing value then
2288
- set responseText to responseText & elementValue & "\\n"
2289
- end if
2290
- on error
2291
- -- Continue to next element
2292
- end try
2293
- end repeat
2294
- if responseText is not "" then
2295
- return responseText
2296
- end if
2297
- on error
2298
- -- Continue to next method
2299
- end try
2300
-
2301
- -- Method 8: Return diagnostic information
2302
- 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."
2303
- end tell
2304
- end tell
2305
- `;
2306
- } else if (ide === 'windsurf') {
2307
- // AppleScript to read the chat response from Windsurf
2308
- appleScript = `
2309
- tell application "System Events"
2310
- tell process "Windsurf"
2311
- set frontmost to true
2312
- delay 1
2313
-
2314
- -- Method 1: Try to get all static text from the window
2315
- try
2316
- set allText to value of static text of window 1
2317
- if allText is not "" then
2318
- return allText
2319
- end if
2320
- on error
2321
- -- Continue to next method
2322
- end try
2323
-
2324
- -- Method 2: Try to get text from groups
2325
- try
2326
- set groups to group of window 1
2327
- set responseText to ""
2328
- repeat with grp in groups
2329
- try
2330
- set groupText to value of static text of grp
2331
- if groupText is not "" then
2332
- set responseText to responseText & groupText & "\\n"
2333
- end if
2334
- on error
2335
- -- Continue to next group
2336
- end try
2337
- end repeat
2338
- if responseText is not "" then
2339
- return responseText
2340
- end if
2341
- on error
2342
- -- Continue to next method
2343
- end try
2344
-
2345
- -- Method 3: Try clipboard approach
2346
- try
2347
- key code 0 using {command down}
2348
- delay 0.5
2349
- key code 8 using {command down}
2350
- delay 0.5
2351
- set clipboardText to (the clipboard)
2352
- if clipboardText is not "" then
2353
- return clipboardText
2354
- end if
2355
- on error
2356
- -- Continue to next method
2357
- end try
2358
-
2359
- -- Method 4: Return placeholder
2360
- return "No chat content found in Windsurf"
2361
- end tell
2362
- end tell
2363
- `;
2364
- } else if (ide === 'vscode') {
2365
- // AppleScript to read the chat response from VS Code
2366
- appleScript = `
2367
- tell application "System Events"
2368
- tell process "Visual Studio Code"
2369
- set frontmost to true
2370
- delay 1
2371
-
2372
- -- Method 1: Try to get all static text from the window (GitHub Copilot Chat)
2373
- try
2374
- set allText to value of static text of window 1
2375
- if allText is not "" then
2376
- return allText
2377
- end if
2378
- on error
2379
- -- Continue to next method
2380
- end try
2381
-
2382
- -- Method 2: Try to get text from groups (chat panels)
2383
- try
2384
- set groups to group of window 1
2385
- set responseText to ""
2386
- repeat with grp in groups
2387
- try
2388
- set groupText to value of static text of grp
2389
- if groupText is not "" then
2390
- set responseText to responseText & groupText & "\\n"
2391
- end if
2392
- on error
2393
- -- Continue to next group
2394
- end try
2395
- end repeat
2396
- if responseText is not "" then
2397
- return responseText
2398
- end if
2399
- on error
2400
- -- Continue to next method
2401
- end try
2402
-
2403
- -- Method 3: Try to get text from text areas (chat input/output areas)
2404
- try
2405
- set textAreas to text area of window 1
2406
- set responseText to ""
2407
- repeat with txtArea in textAreas
2408
- try
2409
- set textValue to value of txtArea
2410
- if textValue is not "" and textValue is not missing value then
2411
- set responseText to responseText & textValue & "\\n"
2412
- end if
2413
- on error
2414
- -- Continue to next text area
2415
- end try
2416
- end repeat
2417
- if responseText is not "" then
2418
- return responseText
2419
- end if
2420
- on error
2421
- -- Continue to next method
2422
- end try
2423
-
2424
- -- Method 4: Try clipboard approach (select all chat content and copy)
2425
- try
2426
- key code 0 using {command down}
2427
- delay 0.5
2428
- key code 8 using {command down}
2429
- delay 0.5
2430
- set clipboardText to (the clipboard)
2431
- if clipboardText is not "" then
2432
- return clipboardText
2433
- end if
2434
- on error
2435
- -- Continue to next method
2436
- end try
2437
-
2438
- -- Method 5: Try to access scroll areas (chat history)
2439
- try
2440
- set scrollAreas to scroll area of window 1
2441
- set responseText to ""
2442
- repeat with scrollArea in scrollAreas
2443
- try
2444
- set scrollTexts to static text of scrollArea
2445
- repeat with txt in scrollTexts
2446
- try
2447
- set textValue to value of txt
2448
- if textValue is not "" and textValue is not missing value then
2449
- set responseText to responseText & textValue & "\\n"
2450
- end if
2451
- on error
2452
- -- Continue to next text
2453
- end try
2454
- end repeat
2455
- on error
2456
- -- Continue to next scroll area
2457
- end try
2458
- end repeat
2459
- if responseText is not "" then
2460
- return responseText
2461
- end if
2462
- on error
2463
- -- Continue to next method
2464
- end try
2465
-
2466
- -- Method 6: Return placeholder
2467
- return "No chat content found in VS Code - ensure GitHub Copilot Chat or Continue extension is active"
2468
- end tell
2469
- end tell
2470
- `;
2471
- } else {
2472
- return `Error: AppleScript reading is not supported for IDE: ${ide}`;
2473
- }
2474
-
2475
- const result = execSync(`osascript -e '${appleScript}'`, { stdio: 'pipe', encoding: 'utf8' });
2476
- this.logger.log(`Successfully read ${ide} response via AppleScript`);
2477
- this.logger.log('📖 AppleScript result:', result);
2478
- this.logger.log('📏 Result length:', result.length);
2479
- this.logger.log('🔍 Result preview:', result.substring(0, 200));
2480
-
2481
- // Check if the result is just diagnostic information
2482
- const isDiagnosticResult = result.includes('DEBUG:') ||
2483
- result.includes('diagnostic') ||
2484
- result.includes('No chat content found') ||
2485
- result.includes('Text areas found: 0') ||
2486
- result.includes('Static text found:') ||
2487
- result.includes('Window count:') ||
2488
- result.includes('Sample text:') ||
2489
- result.includes('Could not read') ||
2490
- result.includes('No readable text content detected');
2491
-
2492
- this.logger.log(`🔍 Diagnostic detection check: isDiagnosticResult = ${isDiagnosticResult}`);
2493
- this.logger.log(`🔍 Result contains 'DEBUG:': ${result.includes('DEBUG:')}`);
2494
- this.logger.log(`🔍 Result contains 'Window count:': ${result.includes('Window count:')}`);
2495
-
2496
- // For Cursor, always try CDP and clipboard methods as fallbacks
2497
- if (ide === 'cursor') {
2498
- this.logger.log('🔧 For Cursor, always trying CDP and clipboard methods as fallbacks...');
2499
-
2500
- // Try CDP method
2501
- try {
2502
- this.logger.log('🔧 Attempting CDP method for Cursor...');
2503
- const cdpResponse = await this.readCursorResponseViaCDP();
2504
- if (cdpResponse && cdpResponse.length > 20) {
2505
- this.logger.log('✅ Successfully read Cursor response via CDP');
2506
- return cdpResponse;
2507
- }
2508
- } catch (error) {
2509
- this.logger.log('⚠️ CDP method failed:', error.message);
2510
- }
2511
-
2512
- // Try clipboard method
2513
- try {
2514
- this.logger.log('🔧 Attempting clipboard-based response detection...');
2515
- const clipboardResponse = await this.readCursorResponseViaClipboard();
2516
- if (clipboardResponse && clipboardResponse.length > 20) {
2517
- this.logger.log('✅ Successfully read Cursor response via clipboard');
2518
- return clipboardResponse;
2519
- }
2520
- } catch (error) {
2521
- this.logger.log('⚠️ Clipboard method failed:', error.message);
2522
- }
2523
- }
2524
-
2525
- if (isDiagnosticResult) {
2526
- this.logger.log('⚠️ AppleScript returned diagnostic info, all methods failed...');
2527
- return `Unable to read Cursor response - all methods failed. AppleScript diagnostic: ${result.substring(0, 200)}...`;
2528
- }
2529
-
2530
- // If we got a very short result, try a diagnostic approach
2531
- if (result.trim().length <= 5) {
2532
- this.logger.log('AppleScript returned very short result, trying diagnostic approach...');
2533
-
2534
- const processName = ide === 'windsurf' ? 'Windsurf' : ide === 'antigravity' ? 'Antigravity' : 'Cursor';
2535
- const diagnosticScript = `
2536
- tell application "System Events"
2537
- tell process "${processName}"
2538
- set frontmost to true
2539
- delay 1
2540
-
2541
- -- Get window info
2542
- set windowInfo to "Window: " & name of window 1 & "\\n"
2543
-
2544
- -- Try to get element counts
2545
- try
2546
- set staticTexts to static text of window 1
2547
- set groups to group of window 1
2548
- set textAreas to text area of window 1
2549
- set scrollAreas to scroll area of window 1
2550
- set uiElements to every UI element of window 1
2551
-
2552
- set elementInfo to "\\nStatic Texts: " & count of staticTexts & "\\n"
2553
- set elementInfo to elementInfo & "Groups: " & count of groups & "\\n"
2554
- set elementInfo to elementInfo & "Text Areas: " & count of textAreas & "\\n"
2555
- set elementInfo to elementInfo & "Scroll Areas: " & count of scrollAreas & "\\n"
2556
- set elementInfo to elementInfo & "UI Elements: " & count of uiElements & "\\n"
2557
-
2558
- -- Try to get sample text from each type
2559
- if count of staticTexts > 0 then
2560
- try
2561
- set sampleText to value of item 1 of staticTexts
2562
- set elementInfo to elementInfo & "\\nSample static text: " & sampleText & "\\n"
2563
- on error
2564
- set elementInfo to elementInfo & "\\nCould not read sample static text\\n"
2565
- end try
2566
- end if
2567
-
2568
- if count of textAreas > 0 then
2569
- try
2570
- set sampleText to value of item 1 of textAreas
2571
- set elementInfo to elementInfo & "\\nSample text area: " & sampleText & "\\n"
2572
- on error
2573
- set elementInfo to elementInfo & "\\nCould not read sample text area\\n"
2574
- end try
2575
- end if
2576
-
2577
- return windowInfo & elementInfo
2578
- on error
2579
- return windowInfo & "\\nCould not get element info"
2580
- end try
2581
- end tell
2582
- end tell
2583
- `;
2584
-
2585
- const diagnosticResult = execSync(`osascript -e '${diagnosticScript}'`, { stdio: 'pipe', encoding: 'utf8' });
2586
- this.logger.log('Diagnostic AppleScript result:', diagnosticResult);
2587
-
2588
- if (diagnosticResult.length > 20) {
2589
- return diagnosticResult.trim();
2590
- }
2591
- }
2592
-
2593
- return result.trim();
2594
-
2595
- } catch (error) {
2596
- this.logger.log('AppleScript reading failed:', error.message);
2597
- return 'Error: Could not read response via AppleScript';
2598
- }
208
+ // This functionality is now handled internally by the core manager
209
+ return await this.coreManager.sendText(text, ide);
2599
210
  }
2600
211
 
2601
212
  /**
2602
213
  * Detect quota warnings using AppleScript
2603
- * @param {string} ide - The IDE name ('windsurf', 'kiro', 'github-copilot', 'amazon-q', 'vscode')
214
+ * @param {string} ide - The IDE name ('windsurf')
2604
215
  * @returns {Promise<Object>} Quota detection result
2605
216
  */
2606
217
  async detectQuotaWarning(ide) {
2607
- // Antigravity has its own dedicated quota check method
2608
- if (ide === 'antigravity') {
2609
- this.logger.log('🔍 Using checkAntigravityQuotaLimit for Antigravity...');
2610
- const result = await this.checkAntigravityQuotaLimit();
2611
- if (result.isRateLimited) {
2612
- const warning = result.resumeAt
2613
- ? `Gemini quota limit reached. Resume at: ${result.resumeAt}`
2614
- : (result.message || 'Gemini quota/rate limit detected');
2615
- this.logger.log(`❌ Antigravity quota detected: ${warning}`);
2616
- return {
2617
- hasQuotaWarning: true,
2618
- warning,
2619
- resumeAt: result.resumeAt,
2620
- note: 'Detected via Antigravity AppleScript UI check'
2621
- };
2622
- }
2623
- return {
2624
- hasQuotaWarning: false,
2625
- note: result.note || 'No Antigravity quota warning detected'
2626
- };
2627
- }
2628
-
2629
- const supportedIdes = ['windsurf', 'kiro', 'github-copilot', 'amazon-q', 'vscode'];
2630
- if (!supportedIdes.includes(ide)) {
2631
- return {
2632
- hasQuotaWarning: false,
2633
- note: `AppleScript quota detection is only supported for: ${supportedIdes.join(', ')}`
2634
- };
2635
- }
2636
-
2637
- // Map IDE to process name
2638
- // Note: Kiro is a VS Code extension, so it runs in the Code process
2639
- const processNameMap = {
2640
- 'windsurf': 'Windsurf',
2641
- 'kiro': 'Code', // Kiro is a VS Code extension
2642
- 'github-copilot': 'Code',
2643
- 'amazon-q': 'Code',
2644
- 'vscode': 'Code'
2645
- };
2646
- let processName = processNameMap[ide] || 'VS Code';
2647
-
2648
- this.logger.log('🔍 === APPLESCRIPT DETECTION START ===');
2649
- this.logger.log(`🔍 Starting enhanced AppleScript quota detection for ${ide} (${processName})...`);
2650
-
2651
- try {
2652
- // Test basic AppleScript execution first
2653
- this.logger.log('🔍 Testing basic AppleScript execution...');
2654
- const basicTest = execSync('osascript -e "return \\"test\\""', { stdio: 'pipe', encoding: 'utf8' });
2655
- this.logger.log('✅ Basic AppleScript test passed:', basicTest.trim());
2656
- } catch (basicError) {
2657
- this.logger.log('❌ Basic AppleScript test failed:', basicError.message);
2658
- return {
2659
- hasQuotaWarning: false,
2660
- error: 'AppleScript not available',
2661
- note: 'Basic AppleScript test failed'
2662
- };
2663
- }
2664
-
2665
- // Try a much simpler AppleScript first
2666
- this.logger.log(`🔍 Trying simple AppleScript approach for ${processName}...`);
2667
- const simpleAppleScript = `
2668
- tell application "System Events"
2669
- tell process "${processName}"
2670
- set frontmost to true
2671
- delay 1
2672
-
2673
- -- Method 1: Look for static text containing quota warnings
2674
- try
2675
- set allText to value of static text of window 1
2676
- -- Windsurf patterns
2677
- if allText contains "Not enough credits" or allText contains "Upgrade to a paid plan" or allText contains "switch to SWE-1-lite" then
2678
- return "QUOTA_WARNING_DETECTED: " & allText
2679
- end if
2680
- -- Kiro patterns
2681
- if allText contains "out of credits" or allText contains "upgrade to a higher plan" or allText contains "wait for next month" then
2682
- return "QUOTA_WARNING_DETECTED: " & allText
2683
- end if
2684
- -- GitHub Copilot / generic patterns
2685
- if allText contains "quota exceeded" or allText contains "credits exhausted" or allText contains "payment required" then
2686
- return "QUOTA_WARNING_DETECTED: " & allText
2687
- end if
2688
- on error
2689
- -- Continue to next method
2690
- end try
2691
-
2692
- -- Method 2: Look for disabled text areas (quota warnings often disable chat input)
2693
- try
2694
- set textAreas to text area of window 1
2695
- repeat with textArea in textAreas
2696
- try
2697
- if not enabled of textArea then
2698
- return "QUOTA_INDICATOR_DETECTED: Disabled text area found"
2699
- end if
2700
- on error
2701
- -- Continue to next text area
2702
- end try
2703
- end repeat
2704
- on error
2705
- -- Continue to next method
2706
- end try
2707
-
2708
- -- Method 3: Look for specific UI elements that indicate quota issues
2709
- try
2710
- set buttons to button of window 1
2711
- repeat with btn in buttons
2712
- try
2713
- set buttonText to title of btn
2714
- if buttonText contains "Upgrade" or buttonText contains "credits" or buttonText contains "quota" then
2715
- return "QUOTA_UI_DETECTED: " & buttonText
2716
- end if
2717
- on error
2718
- -- Continue to next button
2719
- end try
2720
- end repeat
2721
- on error
2722
- -- Continue to next method
2723
- end try
2724
-
2725
- -- Method 4: Look for groups that might contain quota information
2726
- try
2727
- set groups to group of window 1
2728
- repeat with grp in groups
2729
- try
2730
- set groupText to value of static text of grp
2731
- if groupText contains "credits" or groupText contains "quota" or groupText contains "upgrade" then
2732
- return "QUOTA_GROUP_DETECTED: " & groupText
2733
- end if
2734
- on error
2735
- -- Continue to next group
2736
- end try
2737
- end repeat
2738
- on error
2739
- -- Continue to next method
2740
- end try
2741
-
2742
- return "NO_QUOTA_WARNING_DETECTED"
2743
- end tell
2744
- end tell
2745
- `;
2746
-
2747
- this.logger.log('🔍 Simple AppleScript length:', simpleAppleScript.length);
2748
- this.logger.log('🔍 Simple AppleScript preview:', simpleAppleScript.substring(0, 200) + '...');
2749
-
2750
- try {
2751
- this.logger.log('🔍 Executing simple AppleScript...');
2752
- this.logger.log('🔍 AppleScript command length:', simpleAppleScript.length);
2753
-
2754
- const result = execSync(`osascript -e '${simpleAppleScript}'`, { stdio: 'pipe', encoding: 'utf8' });
2755
- this.logger.log('🔍 AppleScript execution completed');
2756
- this.logger.log('🔍 AppleScript result length:', result.length);
2757
- this.logger.log('🔍 AppleScript result:', result.trim());
2758
- this.logger.log('🔍 AppleScript result starts with:', result.trim().substring(0, 100));
2759
-
2760
- // Check for quota warnings in the result
2761
- if (result.includes('QUOTA_WARNING_DETECTED')) {
2762
- this.logger.log('❌ Windsurf quota warning detected via enhanced AppleScript');
2763
- return {
2764
- hasQuotaWarning: true,
2765
- matchedText: result.replace('QUOTA_WARNING_DETECTED: ', '').trim(),
2766
- method: 'applescript-static-text',
2767
- note: 'Windsurf quota warning detected via enhanced AppleScript detection'
2768
- };
2769
- }
2770
-
2771
- if (result.includes('QUOTA_INDICATOR_DETECTED')) {
2772
- this.logger.log('❌ Windsurf quota indicator detected via enhanced AppleScript');
2773
- return {
2774
- hasQuotaWarning: true,
2775
- matchedText: result.replace('QUOTA_INDICATOR_DETECTED: ', '').trim(),
2776
- method: 'applescript-disabled-input',
2777
- note: 'Windsurf quota indicator detected via enhanced AppleScript (disabled chat input)'
2778
- };
2779
- }
2780
-
2781
- if (result.includes('QUOTA_UI_DETECTED') || result.includes('QUOTA_GROUP_DETECTED')) {
2782
- this.logger.log('⚠️ Windsurf potential quota text detected via enhanced AppleScript');
2783
- return {
2784
- hasQuotaWarning: true,
2785
- matchedText: result.replace(/QUOTA_UI_DETECTED: |QUOTA_GROUP_DETECTED: /, '').trim(),
2786
- method: 'applescript-ui-elements',
2787
- note: 'Windsurf potential quota text detected via enhanced AppleScript'
2788
- };
2789
- }
2790
-
2791
- // Additional pattern matching for quota-related text
2792
- const quotaPatterns = [
2793
- 'not enough credits',
2794
- 'upgrade to a paid plan',
2795
- 'switch to swe-1-lite',
2796
- 'quota exceeded',
2797
- 'credits exhausted',
2798
- 'payment required'
2799
- ];
2800
-
2801
- // Only do pattern matching if we didn't get a clear NO_QUOTA_WARNING_DETECTED
2802
- if (!result.includes('NO_QUOTA_WARNING_DETECTED')) {
2803
- const lowerResult = result.toLowerCase();
2804
- for (const pattern of quotaPatterns) {
2805
- if (lowerResult.includes(pattern)) {
2806
- this.logger.log('❌ Windsurf quota warning detected via text pattern matching');
2807
- return {
2808
- hasQuotaWarning: true,
2809
- matchedText: pattern,
2810
- method: 'applescript-pattern-matching',
2811
- pattern: pattern,
2812
- note: 'Windsurf quota warning detected via text pattern matching in AppleScript result'
2813
- };
2814
- }
2815
- }
2816
- }
2817
-
2818
- this.logger.log('🔍 === APPLESCRIPT DETECTION END ===');
2819
- this.logger.log('🔍 Since AppleScript cannot access web view, trying alternative methods...');
2820
-
2821
- // Since AppleScript cannot access web view content, return a note about the limitation
2822
- return {
2823
- hasQuotaWarning: false,
2824
- method: 'applescript-limited',
2825
- note: 'Windsurf quota warning is visible in UI but not detectable via AppleScript'
2826
- };
2827
-
2828
- } catch (error) {
2829
- this.logger.log('❌ AppleScript error:', error.message);
2830
- return {
2831
- hasQuotaWarning: false,
2832
- error: error.message,
2833
- note: 'AppleScript cannot access web view content - Windsurf does not support CDP'
2834
- };
2835
- }
2836
- }
2837
-
2838
- /**
2839
- * Read Cursor response via Chrome DevTools Protocol (CDP)
2840
- * @returns {Promise<string>} The chat response text
2841
- */
2842
- async readCursorResponseViaCDP() {
2843
- try {
2844
- this.logger.log('🔧 Attempting CDP connection to Cursor on port 9225...');
2845
-
2846
- // Import CDP dynamically to avoid issues in VSCode extension context
2847
- const CDP = require('chrome-remote-interface');
2848
-
2849
- // List available targets
2850
- const targets = await CDP.List({ port: 9225 });
2851
- this.logger.log(`🔧 Found ${targets.length} CDP targets`);
2852
-
2853
- // Find the main workbench target (not settings)
2854
- const workbench = targets.find(t =>
2855
- t.title !== 'Cursor Settings' &&
2856
- t.type === 'page' &&
2857
- t.url && t.url.includes('workbench')
2858
- ) || targets[0];
2859
-
2860
- if (!workbench) {
2861
- throw new Error('No suitable Cursor workbench target found');
2862
- }
2863
-
2864
- this.logger.log(`🔧 Connecting to target: ${workbench.title}`);
2865
-
2866
- // Connect to the target
2867
- const client = await CDP({ port: 9225, target: workbench });
2868
- const { Runtime, DOM } = client;
2869
-
2870
- // Enable required domains
2871
- await Runtime.enable();
2872
- await DOM.enable();
2873
-
2874
- this.logger.log('🔧 CDP connection established, searching for chat content...');
2875
-
2876
- // Try multiple selectors to find chat content
2877
- const selectors = [
2878
- '.chat-content',
2879
- '.chat-messages',
2880
- '.response-content',
2881
- '.ai-response',
2882
- '.message-content',
2883
- '[data-testid="chat-message"]',
2884
- '.markdown-content',
2885
- '.prose',
2886
- '.chat-panel',
2887
- '.sidebar-panel'
2888
- ];
2889
-
2890
- for (const selector of selectors) {
2891
- try {
2892
- this.logger.log(`🔧 Trying selector: ${selector}`);
2893
-
2894
- // Get document root
2895
- const { root } = await DOM.getDocument();
2896
-
2897
- // Search for elements with the selector
2898
- const { nodeIds } = await DOM.querySelectorAll({ nodeId: root.nodeId, selector });
2899
-
2900
- if (nodeIds.length > 0) {
2901
- this.logger.log(`🔧 Found ${nodeIds.length} elements with selector: ${selector}`);
2902
-
2903
- // Get text content from the first matching element
2904
- const { nodeId } = await DOM.describeNode({ nodeId: nodeIds[0] });
2905
- const { outerHTML } = await DOM.getOuterHTML({ nodeId });
2906
-
2907
- // Extract text content (remove HTML tags)
2908
- const textContent = outerHTML.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
2909
-
2910
- if (textContent && textContent.length > 20) {
2911
- this.logger.log(`✅ Successfully extracted chat content via CDP: ${textContent.substring(0, 100)}...`);
2912
- await client.close();
2913
- return textContent;
2914
- }
2915
- }
2916
- } catch (error) {
2917
- this.logger.log(`⚠️ Selector ${selector} failed: ${error.message}`);
2918
- }
2919
- }
2920
-
2921
- // If no specific selectors work, try to get all text content
2922
- this.logger.log('🔧 Trying to get all text content from the page...');
2923
-
2924
- const { root } = await DOM.getDocument();
2925
- const { outerHTML } = await DOM.getOuterHTML({ nodeId: root.nodeId });
2926
-
2927
- // Extract text content and look for AI responses
2928
- const textContent = outerHTML.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
2929
-
2930
- // Look for patterns that indicate AI responses
2931
- const aiResponsePatterns = [
2932
- /(?:AI|Assistant|Claude|GPT|Response):\s*([^]*?)(?=\n\n|\n[A-Z]|$)/gi,
2933
- /(?:Here|This|The answer|The solution):\s*([^]*?)(?=\n\n|\n[A-Z]|$)/gi
2934
- ];
2935
-
2936
- for (const pattern of aiResponsePatterns) {
2937
- const matches = textContent.match(pattern);
2938
- if (matches && matches.length > 0) {
2939
- const response = matches[matches.length - 1]; // Get the latest response
2940
- if (response && response.length > 20) {
2941
- this.logger.log(`✅ Found AI response via CDP pattern matching: ${response.substring(0, 100)}...`);
2942
- await client.close();
2943
- return response;
2944
- }
2945
- }
2946
- }
2947
-
2948
- await client.close();
2949
- throw new Error('No chat content found via CDP');
2950
-
2951
- } catch (error) {
2952
- this.logger.log(`❌ CDP method failed: ${error.message}`);
2953
- throw error;
2954
- }
2955
- }
2956
-
2957
- /**
2958
- * Read Cursor response via clipboard (select all and copy)
2959
- * @returns {Promise<string>} The chat response text
2960
- */
2961
- async readCursorResponseViaClipboard() {
2962
- try {
2963
- this.logger.log('🔧 Attempting clipboard-based response detection...');
2964
-
2965
- // AppleScript to select all content in Cursor and copy to clipboard
2966
- const clipboardScript = `
2967
- tell application "System Events"
2968
- tell process "Cursor"
2969
- set frontmost to true
2970
- delay 1
2971
-
2972
- -- Try to focus on the chat area first without selecting all content
2973
- try
2974
- -- Just try to copy without selecting all (safer approach)
2975
- key code 8 using {command down} -- Cmd+C to copy
2976
- delay 0.5
2977
- on error
2978
- -- If that fails, return empty response
2979
- return ""
2980
- end try
2981
- end tell
2982
- end tell
2983
- `;
2984
-
2985
- // Use file-based execution for clipboard script too
2986
- const tempFile = join(tmpdir(), `clipboard_${Date.now()}.scpt`);
2987
- try {
2988
- writeFileSync(tempFile, clipboardScript, 'utf8');
2989
- execSync(`osascript "${tempFile}"`, { stdio: 'pipe' });
2990
- } finally {
2991
- try {
2992
- unlinkSync(tempFile);
2993
- } catch (cleanupError) {
2994
- this.logger.log(`⚠️ Failed to cleanup clipboard temp file: ${cleanupError.message}`);
2995
- }
2996
- }
2997
-
2998
- // Read from clipboard
2999
- const clipboardScript2 = `
3000
- the clipboard
3001
- `;
3002
-
3003
- const tempFile2 = join(tmpdir(), `clipboard_read_${Date.now()}.scpt`);
3004
- try {
3005
- writeFileSync(tempFile2, clipboardScript2, 'utf8');
3006
- const clipboardContent = execSync(`osascript "${tempFile2}"`, { stdio: 'pipe', encoding: 'utf8' });
3007
-
3008
- if (clipboardContent && clipboardContent.trim().length > 20) {
3009
- this.logger.log(`✅ Successfully read clipboard content: ${clipboardContent.substring(0, 100)}...`);
3010
- return clipboardContent.trim();
3011
- }
3012
-
3013
- throw new Error('Clipboard content is empty or too short');
3014
- } finally {
3015
- try {
3016
- unlinkSync(tempFile2);
3017
- } catch (cleanupError) {
3018
- this.logger.log(`⚠️ Failed to cleanup clipboard read temp file: ${cleanupError.message}`);
3019
- }
3020
- }
3021
-
3022
- } catch (error) {
3023
- this.logger.log(`❌ Clipboard method failed: ${error.message}`);
3024
- throw error;
3025
- }
218
+ return await this.coreManager.detectQuotaWarning(ide);
3026
219
  }
3027
220
 
3028
221
  /**
@@ -3030,73 +223,7 @@ class AppleScriptManager {
3030
223
  * @returns {Promise<Object>} Result object with success status and details
3031
224
  */
3032
225
  async closeClaudeCLI() {
3033
- try {
3034
- this.logger.log('🔄 [Claude] Closing Claude CLI terminal windows...');
3035
-
3036
- const script = `
3037
- tell application "Terminal"
3038
- activate
3039
- delay 1
3040
-
3041
- -- Find all terminal windows with Claude CLI running
3042
- set claudeWindows to {}
3043
- repeat with w in windows
3044
- try
3045
- set windowName to name of w
3046
- if windowName contains "claude" or windowName contains "Claude" then
3047
- set end of claudeWindows to w
3048
- end if
3049
- end try
3050
- end repeat
3051
-
3052
- -- Close Claude terminal windows
3053
- if (count of claudeWindows) > 0 then
3054
- repeat with w in claudeWindows
3055
- try
3056
- close w
3057
- delay 0.5
3058
- end try
3059
- end repeat
3060
- return "Closed " & (count of claudeWindows) & " Claude terminal windows"
3061
- else
3062
- return "No Claude terminal windows found to close"
3063
- end if
3064
- end tell
3065
- `;
3066
-
3067
- // Create a temporary AppleScript file
3068
- const tempScriptPath = join(tmpdir(), `close_claude_${Date.now()}.scpt`);
3069
- writeFileSync(tempScriptPath, script);
3070
-
3071
- // Execute the AppleScript file
3072
- this.logger.log('🔧 Executing AppleScript to close Claude CLI terminal windows...');
3073
- const result = execSync(`osascript "${tempScriptPath}"`, {
3074
- encoding: 'utf8',
3075
- timeout: 10000
3076
- });
3077
-
3078
- // Clean up the temporary file
3079
- try {
3080
- unlinkSync(tempScriptPath);
3081
- } catch (cleanupError) {
3082
- this.logger.log(`⚠️ Failed to cleanup temp file: ${cleanupError.message}`);
3083
- }
3084
-
3085
- this.logger.log(`✅ Claude CLI close result: ${result.trim()}`);
3086
- return {
3087
- success: true,
3088
- message: result.trim(),
3089
- method: 'applescript',
3090
- action: 'close'
3091
- };
3092
- } catch (error) {
3093
- this.logger.log(`⚠️ Error closing Claude CLI: ${error.message}`);
3094
- return {
3095
- success: false,
3096
- error: error.message,
3097
- method: 'applescript'
3098
- };
3099
- }
226
+ return await this.coreManager.closePreviousChatThread('claude');
3100
227
  }
3101
228
 
3102
229
  /**
@@ -3105,162 +232,7 @@ end tell
3105
232
  * @returns {Promise<Object>} Result object with success status and details
3106
233
  */
3107
234
  async closePreviousChatThread(ide) {
3108
- if (!ide || typeof ide !== 'string') {
3109
- return {
3110
- success: false,
3111
- error: 'Invalid IDE parameter',
3112
- method: 'applescript'
3113
- };
3114
- }
3115
-
3116
- const ideName = ide.toLowerCase();
3117
-
3118
- // Handle Claude CLI separately since it's a terminal application
3119
- if (ideName === 'claude' || ideName === 'claude-code') {
3120
- return await this.closeClaudeCLI();
3121
- }
3122
-
3123
- if (!['cursor', 'vscode', 'windsurf'].includes(ideName)) {
3124
- return {
3125
- success: false,
3126
- error: 'Unsupported IDE for thread closure',
3127
- method: 'applescript'
3128
- };
3129
- }
3130
-
3131
- try {
3132
- this.logger.log(`🔄 [${ideName}] Closing previous chat thread...`);
3133
-
3134
- let script = '';
3135
- switch (ideName) {
3136
- case 'cursor':
3137
- script = `
3138
- tell application "Cursor"
3139
- activate
3140
- delay 1
3141
- end tell
3142
-
3143
- tell application "System Events"
3144
- tell process "Cursor"
3145
- set frontmost to true
3146
- delay 1
3147
-
3148
- -- Close previous chat thread by starting a new conversation
3149
- try
3150
- -- Open Command Palette (Cmd+Shift+P)
3151
- key code 35 using {command down, shift down} -- Cmd+Shift+P
3152
- delay 0.5
3153
-
3154
- -- Type "New Chat" or "Start New Conversation"
3155
- keystroke "New Chat"
3156
- delay 0.5
3157
-
3158
- -- Press Enter to start new conversation
3159
- key code 36 -- Enter
3160
- delay 1
3161
-
3162
- return "Previous chat thread closed in Cursor"
3163
- on error
3164
- -- Fallback: just return success without clearing anything
3165
- return "Previous chat thread closed in Cursor (fallback)"
3166
- end try
3167
- end tell
3168
- end tell
3169
- `;
3170
- break;
3171
-
3172
- case 'vscode':
3173
- script = `
3174
- tell application "Visual Studio Code"
3175
- activate
3176
- delay 1.5
3177
- end tell
3178
-
3179
- tell application "System Events"
3180
- tell process "Visual Studio Code"
3181
- set frontmost to true
3182
- delay 1.5
3183
-
3184
- -- Close previous chat thread in VS Code
3185
- try
3186
- -- Open Command Palette (Cmd+Shift+P)
3187
- key code 35 using {command down, shift down} -- Cmd+Shift+P
3188
- delay 1.0
3189
-
3190
- -- Type "New Chat" or "Start New Conversation"
3191
- keystroke "New Chat"
3192
- delay 1.0
3193
-
3194
- -- Press Enter to start new conversation
3195
- key code 36 -- Enter
3196
- delay 1.5
3197
-
3198
- return "Previous chat thread closed in VS Code"
3199
- on error
3200
- -- Fallback: just return success without clearing anything
3201
- return "Previous chat thread closed in VS Code (fallback)"
3202
- end try
3203
- end tell
3204
- end tell
3205
- `;
3206
- break;
3207
-
3208
- case 'windsurf':
3209
- script = `
3210
- tell application "Windsurf"
3211
- activate
3212
- delay 1
3213
- end tell
3214
-
3215
- tell application "System Events"
3216
- tell process "Windsurf"
3217
- set frontmost to true
3218
- delay 0.5
3219
-
3220
- -- Cmd+Shift+I opens a fresh Cascade conversation and focuses the chat input
3221
- key code 34 using {command down, shift down}
3222
- delay 1.5
3223
-
3224
- return "New Cascade conversation opened in Windsurf"
3225
- end tell
3226
- end tell
3227
- `;
3228
- break;
3229
- }
3230
-
3231
- // Create a temporary AppleScript file
3232
- const tempScriptPath = join(tmpdir(), `close_thread_${Date.now()}.scpt`);
3233
- writeFileSync(tempScriptPath, script);
3234
-
3235
- // Execute the AppleScript file
3236
- this.logger.log(`🔧 Executing AppleScript to close previous thread in ${ideName}...`);
3237
- const result = execSync(`osascript "${tempScriptPath}"`, {
3238
- encoding: 'utf8',
3239
- timeout: 10000
3240
- });
3241
-
3242
- // Clean up the temporary file
3243
- try {
3244
- unlinkSync(tempScriptPath);
3245
- } catch (cleanupError) {
3246
- this.logger.log(`⚠️ Failed to cleanup temp file: ${cleanupError.message}`);
3247
- }
3248
-
3249
- this.logger.log(`✅ Previous chat thread closed in ${ideName}: ${result.trim()}`);
3250
- return {
3251
- success: true,
3252
- message: result.trim(),
3253
- method: 'applescript'
3254
- };
3255
-
3256
- } catch (error) {
3257
- this.logger.log(`❌ Failed to close previous chat thread in ${ideName}: ${error.message}`);
3258
- return {
3259
- success: false,
3260
- error: error.message,
3261
- method: 'applescript'
3262
- };
3263
- }
235
+ return await this.coreManager.closePreviousChatThread(ide);
3264
236
  }
3265
237
 
3266
238
  /**
@@ -3270,229 +242,7 @@ end tell
3270
242
  * @returns {Promise<Object>} Result object with success status and details
3271
243
  */
3272
244
  async sendTextWithThreadClosure(text, ide) {
3273
- if (!text || typeof text !== 'string' || !text.trim()) {
3274
- return {
3275
- success: false,
3276
- error: 'Invalid text parameter',
3277
- method: 'applescript'
3278
- };
3279
- }
3280
-
3281
- if (!ide || typeof ide !== 'string') {
3282
- return {
3283
- success: false,
3284
- error: 'Invalid IDE parameter',
3285
- method: 'applescript'
3286
- };
3287
- }
3288
-
3289
- try {
3290
- this.logger.log(`🔄 [${ide}] Sending text with thread closure...`);
3291
-
3292
- // First, try to close the previous chat thread
3293
- const closeResult = await this.closePreviousChatThread(ide);
3294
-
3295
- // Wait a moment for the thread closure to complete
3296
- await new Promise(resolve => setTimeout(resolve, 1000));
3297
-
3298
- // Then send the new message
3299
- const sendResult = await this.sendText(text, ide);
3300
-
3301
- // Combine the results
3302
- if (sendResult.success) {
3303
- return {
3304
- success: true,
3305
- message: sendResult.message,
3306
- method: sendResult.method,
3307
- threadClosure: closeResult.success ? 'Previous thread closed' : 'Thread closure failed',
3308
- warning: !closeResult.success ? `Failed to close previous thread: ${closeResult.error}` : undefined
3309
- };
3310
- } else {
3311
- return {
3312
- success: false,
3313
- error: sendResult.error,
3314
- method: sendResult.method,
3315
- threadClosure: closeResult.success ? 'Previous thread closed' : 'Thread closure failed'
3316
- };
3317
- }
3318
-
3319
- } catch (error) {
3320
- this.logger.log(`❌ Failed to send text with thread closure to ${ide}: ${error.message}`);
3321
- return {
3322
- success: false,
3323
- error: error.message,
3324
- method: 'applescript'
3325
- };
3326
- }
3327
- }
3328
-
3329
- /**
3330
- * Programmatically sets the state of the Gemini "Code Assistance" feature in VS Code's settings.json.
3331
- * This is more reliable than UI automation.
3332
- * @param {boolean} enable - True to enable, false to disable.
3333
- * @returns {Promise<Object>} Result object with success status and details.
3334
- * @private
3335
- */
3336
- async _setGeminiAgentState(enable) {
3337
- const ideName = 'VS Code';
3338
- const action = enable ? 'Enabling' : 'Disabling';
3339
- this.logger.log(`🚀 [${ideName}] ${action} Gemini Code Assistance by editing settings.json...`);
3340
- try {
3341
- const fs = require('fs');
3342
- const path = require('path');
3343
- const os = require('os');
3344
-
3345
- // Determine the path to settings.json based on the OS
3346
- const homeDir = os.homedir();
3347
- let settingsPath;
3348
- if (this.platform === 'darwin') {
3349
- settingsPath = path.join(homeDir, 'Library', 'Application Support', 'Code', 'User', 'settings.json');
3350
- } else if (this.platform === 'win32') {
3351
- settingsPath = path.join(homeDir, 'AppData', 'Roaming', 'Code', 'User', 'settings.json');
3352
- } else { // Linux
3353
- settingsPath = path.join(homeDir, '.config', 'Code', 'User', 'settings.json');
3354
- }
3355
-
3356
- this.logger.log(`📝 Located settings.json at: ${settingsPath}`);
3357
-
3358
- if (!fs.existsSync(settingsPath)) {
3359
- throw new Error(`VS Code settings.json not found at ${settingsPath}`);
3360
- }
3361
-
3362
- // Read and parse the settings file, handling comments
3363
- const settingsContent = fs.readFileSync(settingsPath, 'utf8');
3364
- // A simple regex to strip comments before parsing
3365
- const jsonContent = settingsContent.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g) => g ? "" : m);
3366
- const settings = JSON.parse(jsonContent);
3367
-
3368
- // Update the setting
3369
- const settingKey = 'gemini.codeAssistance';
3370
- settings[settingKey] = enable;
3371
-
3372
- // Write the updated settings back to the file
3373
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
3374
-
3375
- this.logger.log(`✅ [${ideName}] Successfully set "${settingKey}": ${enable} in settings.json.`);
3376
- return {
3377
- success: true,
3378
- method: 'settings.json',
3379
- message: `Gemini Code Assistance ${action.toLowerCase()}d successfully.`
3380
- };
3381
- } catch (error) {
3382
- this.logger.error(`❌ [${ideName}] Error toggling Gemini Agent via settings.json:`, error.message);
3383
- return {
3384
- success: false,
3385
- error: error.message,
3386
- method: 'settings.json'
3387
- };
3388
- }
3389
- }
3390
-
3391
-
3392
- /**
3393
- * Check if we're running within a VS Code extension context
3394
- * @returns {boolean} True if running in VS Code extension
3395
- */
3396
- isRunningInVSCodeExtension() {
3397
- try {
3398
- // Check for VS Code extension environment variables
3399
- return !!(process.env.VSCODE_PID ||
3400
- process.env.VSCODE_INJECTION ||
3401
- process.env.VSCODE_IPC_HOOK_CLI ||
3402
- (typeof require !== 'undefined' && require.main && require.main.filename.includes('vscode')));
3403
- } catch (error) {
3404
- this.logger.log('⚠️ Could not determine VS Code extension context:', error.message);
3405
- return false;
3406
- }
3407
- }
3408
-
3409
- /**
3410
- * Send text to Windsurf extension within VS Code
3411
- * @param {string} text - The text to send
3412
- * @returns {Promise<Object>} Result object with success status
3413
- */
3414
- async sendTextToWindsurfExtension(text) {
3415
- try {
3416
- this.logger.log('🚀 Sending message to Windsurf extension via VS Code commands...');
3417
-
3418
- // Since we're in the core package, we can't directly use vscode.commands
3419
- // Instead, we'll use AppleScript to interact with VS Code
3420
- const appleScript = `
3421
- tell application "System Events"
3422
- tell process "Visual Studio Code"
3423
- set frontmost to true
3424
- delay 1
3425
-
3426
- -- Step 1: Open Command Palette (Cmd+Shift+P)
3427
- key code 35 using {command down, shift down}
3428
- delay 0.5
3429
-
3430
- -- Step 2: Type "Windsurf: Open Chat"
3431
- keystroke "Windsurf: Open Chat"
3432
- delay 0.5
3433
-
3434
- -- Step 3: Press Enter to execute the command
3435
- key code 36
3436
- delay 1.5
3437
-
3438
- -- Step 4: Try to start a new chat session
3439
- try
3440
- -- Open Command Palette again
3441
- key code 35 using {command down, shift down}
3442
- delay 0.5
3443
-
3444
- -- Type the new chat command
3445
- keystroke "extension.windSurf.newChat"
3446
- delay 0.5
3447
-
3448
- -- Press Enter
3449
- key code 36
3450
- delay 1
3451
- on error
3452
- -- Continue if new chat command fails
3453
- end try
3454
-
3455
- -- Step 5: Send the message
3456
- keystroke "${text.replace(/"/g, '\\"')}"
3457
- delay 0.5
3458
-
3459
- -- Step 6: Submit the message (Enter)
3460
- key code 36
3461
- delay 0.5
3462
-
3463
- return "Message sent to Windsurf extension"
3464
- end tell
3465
- end tell
3466
- `;
3467
-
3468
- // Use file-based execution for AppleScript
3469
- const tempFile = join(tmpdir(), `windsurf_extension_${Date.now()}.scpt`);
3470
- try {
3471
- writeFileSync(tempFile, appleScript, 'utf8');
3472
- execSync(`osascript "${tempFile}"`, { stdio: 'pipe' });
3473
- this.logger.log('✅ Message sent to Windsurf extension successfully');
3474
- return {
3475
- success: true,
3476
- method: 'applescript-vscode-commands',
3477
- message: `Message sent to Windsurf extension: ${text}`,
3478
- note: 'Message sent via AppleScript automation to VS Code Windsurf extension'
3479
- };
3480
- } finally {
3481
- try {
3482
- unlinkSync(tempFile);
3483
- } catch (cleanupError) {
3484
- this.logger.log(`⚠️ Failed to cleanup temp file: ${cleanupError.message}`);
3485
- }
3486
- }
3487
-
3488
- } catch (error) {
3489
- this.logger.log('❌ Failed to send message to Windsurf extension:', error);
3490
- return {
3491
- success: false,
3492
- error: `Failed to send message to Windsurf extension: ${error.message}`,
3493
- method: 'applescript-vscode-commands'
3494
- };
3495
- }
245
+ return await this.coreManager.sendTextWithThreadClosure(text, ide);
3496
246
  }
3497
247
 
3498
248
  /**
@@ -3501,312 +251,52 @@ end tell
3501
251
  * @returns {Promise<Object>} Result object with success status and details
3502
252
  */
3503
253
  async getIDEStatus(ide) {
3504
- if (!ide || typeof ide !== 'string') {
3505
- return {
3506
- success: false,
3507
- error: 'Invalid IDE name provided',
3508
- running: false
3509
- };
3510
- }
3511
-
3512
- try {
3513
- this.logger.log(`🔍 Checking status of ${ide}...`);
3514
-
3515
- switch (ide.toLowerCase()) {
3516
- case 'cursor':
3517
- return await this._checkCursorStatus();
3518
- case 'vscode':
3519
- return await this._checkVSCodeStatus();
3520
- case 'windsurf':
3521
- return await this._checkWindsurfStatus();
3522
- case 'antigravity':
3523
- return await this._checkAntigravityStatus();
3524
- case 'cline':
3525
- return await this._checkClineStatus();
3526
- default:
3527
- return {
3528
- success: false,
3529
- error: `Unsupported IDE: ${ide}`,
3530
- running: false
3531
- };
3532
- }
3533
- } catch (error) {
3534
- return {
3535
- success: false,
3536
- error: `Failed to check IDE status: ${error.message}`,
3537
- running: false
3538
- };
3539
- }
254
+ return await this.coreManager.getIDEStatus(ide);
3540
255
  }
3541
256
 
3542
257
  /**
3543
- * Check Cursor status
3544
- * @private
258
+ * Check Windsurf for rate limit errors
259
+ * Reads Windsurf chat response to detect rate limit messages
260
+ * @returns {Promise<Object>} Result object with isRateLimited flag and details
3545
261
  */
3546
- async _checkCursorStatus() {
3547
- try {
3548
- const script = `
3549
- tell application "System Events"
3550
- set isRunning to (exists process "Cursor")
3551
- if isRunning then
3552
- try
3553
- tell application "Cursor" to activate
3554
- delay 0.5
3555
- return "running"
3556
- on error
3557
- return "error"
3558
- end try
3559
- else
3560
- return "not_running"
3561
- end if
3562
- end tell
3563
- `;
3564
-
3565
- const result = this.appleScriptUtils.executeAppleScript(script);
3566
- const running = result.includes('running');
3567
-
3568
- return {
3569
- success: true,
3570
- running,
3571
- status: running ? 'running' : 'not_running',
3572
- message: running ? 'Cursor is running and accessible' : 'Cursor is not running'
3573
- };
3574
- } catch (error) {
3575
- return {
3576
- success: false,
3577
- error: `Failed to check Cursor status: ${error.message}`,
3578
- running: false
3579
- };
3580
- }
262
+ async checkWindsurfRateLimit() {
263
+ return await this.coreManager.checkWindsurfRateLimit();
3581
264
  }
3582
265
 
3583
266
  /**
3584
- * Check VS Code status
3585
- * @private
3586
- */
3587
- async _checkVSCodeStatus() {
3588
- try {
3589
- const script = `
3590
- tell application "System Events"
3591
- set isRunning to (exists process "Code")
3592
- if isRunning then
3593
- try
3594
- tell application "Visual Studio Code" to activate
3595
- delay 0.5
3596
- return "running"
3597
- on error
3598
- return "error"
3599
- end try
3600
- else
3601
- return "not_running"
3602
- end if
3603
- end tell
3604
- `;
3605
-
3606
- const result = this.appleScriptUtils.executeAppleScript(script);
3607
- const running = result.includes('running');
3608
-
3609
- return {
3610
- success: true,
3611
- running,
3612
- status: running ? 'running' : 'not_running',
3613
- message: running ? 'VS Code is running and accessible' : 'VS Code is not running'
3614
- };
3615
- } catch (error) {
3616
- return {
3617
- success: false,
3618
- error: `Failed to check VS Code status: ${error.message}`,
3619
- running: false
3620
- };
3621
- }
3622
- }
3623
-
3624
- /**
3625
- * Check Windsurf status
3626
- * @private
3627
- */
3628
- async _checkWindsurfStatus() {
3629
- try {
3630
- const script = `
3631
- tell application "System Events"
3632
- set isRunning to (exists process "Windsurf")
3633
- if isRunning then
3634
- try
3635
- tell application "Windsurf" to activate
3636
- delay 0.5
3637
- return "running"
3638
- on error
3639
- return "error"
3640
- end try
3641
- else
3642
- return "not_running"
3643
- end if
3644
- end tell
3645
- `;
3646
-
3647
- const result = this.appleScriptUtils.executeAppleScript(script);
3648
- const running = result.includes('running');
3649
-
3650
- return {
3651
- success: true,
3652
- running,
3653
- status: running ? 'running' : 'not_running',
3654
- message: running ? 'Windsurf is running and accessible' : 'Windsurf is not running'
3655
- };
3656
- } catch (error) {
3657
- return {
3658
- success: false,
3659
- error: `Failed to check Windsurf status: ${error.message}`,
3660
- running: false
3661
- };
3662
- }
3663
- }
3664
-
3665
- /**
3666
- * Check Windsurf quota limit
3667
- * @private
267
+ * Check for continuation prompts using AppleScript UI queries
268
+ * @param {string} ide - The IDE name
269
+ * @returns {Promise<Object>} Result with continuation detected and button info
3668
270
  */
3669
- async _checkWindsurfQuotaLimit() {
3670
- try {
3671
- // Check for Windsurf-specific quota warnings in UI
3672
- const script = `
3673
- tell application "System Events"
3674
- set isRunning to (exists process "Windsurf")
3675
- if isRunning then
3676
- try
3677
- tell application "Windsurf" to activate
3678
- delay 0.5
3679
-
3680
- -- Check for quota warnings in UI
3681
- tell application "Windsurf" to get title of front window
3682
- set windowTitle to title of front window
3683
-
3684
- -- Look for common quota warning patterns
3685
- if windowTitle contains "out of credits" or windowTitle contains "upgrade plan" or windowTitle contains "monthly usage limit" then
3686
- return "QUOTA_WARNING_DETECTED"
3687
- end if
3688
-
3689
- delay 0.5
3690
- return "NO_QUOTA_WARNING_DETECTED"
3691
- on error
3692
- return "ERROR_CHECKING_QUOTA"
3693
- end if
3694
- else
3695
- return "NOT_RUNNING"
3696
- end if
3697
- end tell
3698
- `;
3699
-
3700
- const result = this.appleScriptUtils.executeAppleScript(script);
3701
-
3702
- return {
3703
- success: true,
3704
- hasQuotaWarning: result.includes('QUOTA_WARNING_DETECTED'),
3705
- matchedText: result
3706
- };
3707
- } catch (error) {
3708
- return {
3709
- success: false,
3710
- error: `Failed to check Windsurf quota: ${error.message}`,
3711
- hasQuotaWarning: false
3712
- };
3713
- }
271
+ async checkForContinuation(ide) {
272
+ return await this.coreManager.checkForContinuation(ide);
3714
273
  }
3715
274
 
3716
275
  /**
3717
- * Check Windsurf quota limit (public wrapper)
3718
- * @returns {Promise<{hasQuotaWarning: boolean, matchedText?: string, error?: string}>}
276
+ * Click a continuation button if found
277
+ * @param {string} ide - The IDE name
278
+ * @returns {Promise<Object>} Result of operation
3719
279
  */
3720
- async checkWindsurfQuotaLimit() {
3721
- return this._checkWindsurfQuotaLimit();
280
+ async clickContinuationButton(ide) {
281
+ return await this.coreManager.clickContinuationButton(ide);
3722
282
  }
3723
283
 
3724
284
  /**
3725
- * Check Antigravity status
3726
- * @private
285
+ * Check if we're running within a VS Code extension context
286
+ * @returns {boolean} True if running in VS Code extension
3727
287
  */
3728
- async _checkAntigravityStatus() {
3729
- try {
3730
- // Antigravity runs in browser, check if Chrome/Edge is running with Antigravity tab
3731
- const script = `
3732
- tell application "System Events"
3733
- set isChromeRunning to (exists process "Google Chrome")
3734
- set isEdgeRunning to (exists process "Microsoft Edge")
3735
-
3736
- if isChromeRunning then
3737
- try
3738
- tell application "Google Chrome" to activate
3739
- delay 0.5
3740
- return "running"
3741
- on error
3742
- return "error"
3743
- end try
3744
- else if isEdgeRunning then
3745
- try
3746
- tell application "Microsoft Edge" to activate
3747
- delay 0.5
3748
- return "running"
3749
- on error
3750
- return "error"
3751
- end try
3752
- else
3753
- return "not_running"
3754
- end if
3755
- end tell
3756
- `;
3757
-
3758
- const result = this.appleScriptUtils.executeAppleScript(script);
3759
- const running = result.includes('running');
3760
-
3761
- return {
3762
- success: true,
3763
- running,
3764
- status: running ? 'running' : 'not_running',
3765
- message: running ? 'Browser is running (Antigravity accessible)' : 'Browser is not running'
3766
- };
3767
- } catch (error) {
3768
- return {
3769
- success: false,
3770
- error: `Failed to check Antigravity status: ${error.message}`,
3771
- running: false
3772
- };
3773
- }
288
+ isRunningInVSCodeExtension() {
289
+ return this.coreManager.isRunningInVSCodeExtension();
3774
290
  }
3775
291
 
3776
292
  /**
3777
- * Check Cline status
3778
- * @private
293
+ * Send text to Windsurf extension within VS Code
294
+ * @param {string} text - The text to send
295
+ * @returns {Promise<Object>} Result object with success status
3779
296
  */
3780
- async _checkClineStatus() {
3781
- try {
3782
- // Cline runs in VS Code as an extension
3783
- const vscodeStatus = await this._checkVSCodeStatus();
3784
-
3785
- if (!vscodeStatus.running) {
3786
- return {
3787
- success: true,
3788
- running: false,
3789
- status: 'not_running',
3790
- message: 'Cline requires VS Code to be running'
3791
- };
3792
- }
3793
-
3794
- // For now, assume Cline is available if VS Code is running
3795
- // In a more sophisticated implementation, we could check if the extension is installed
3796
- return {
3797
- success: true,
3798
- running: true,
3799
- status: 'running',
3800
- message: 'Cline is available (VS Code running)'
3801
- };
3802
- } catch (error) {
3803
- return {
3804
- success: false,
3805
- error: `Failed to check Cline status: ${error.message}`,
3806
- running: false
3807
- };
3808
- }
297
+ async sendTextToWindsurfExtension(text) {
298
+ return await this.coreManager.sendTextToWindsurfExtension(text);
3809
299
  }
3810
300
  }
3811
301
 
3812
- module.exports = AppleScriptManager;
302
+ export { AppleScriptManager };