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.
- package/README.md +240 -0
- package/package.json +10 -2
- package/src/agents/Agent.js +300 -0
- package/src/agents/AgentAdditionService.js +311 -0
- package/src/agents/AgentCheckService.js +690 -0
- package/src/agents/AgentInstallationService.js +140 -0
- package/src/agents/AgentSetupService.js +467 -0
- package/src/agents/AgentStatus.js +183 -0
- package/src/agents/AgentVerificationService.js +634 -0
- package/src/agents/ConfigurationSchemaValidator.js +543 -0
- package/src/agents/EnvironmentConfigurationManager.js +602 -0
- package/src/agents/InstallationErrorHandler.js +372 -0
- package/src/agents/InstallationLog.js +363 -0
- package/src/agents/InstallationMethod.js +510 -0
- package/src/agents/InstallationOrchestrator.js +352 -0
- package/src/agents/InstallationProgressReporter.js +372 -0
- package/src/agents/InstallationRetryManager.js +322 -0
- package/src/agents/InstallationType.js +254 -0
- package/src/agents/OperationTypes.js +310 -0
- package/src/agents/PerformanceMetricsCollector.js +493 -0
- package/src/agents/SecurityValidationService.js +534 -0
- package/src/agents/VerificationTest.js +354 -0
- package/src/agents/VerificationType.js +226 -0
- package/src/agents/WindowsPermissionHandler.js +518 -0
- package/src/agents/config/AgentConfigManager.js +393 -0
- package/src/agents/config/AgentDefaultsRegistry.js +373 -0
- package/src/agents/config/ConfigValidator.js +281 -0
- package/src/agents/discovery/AgentDiscoveryService.js +707 -0
- package/src/agents/logging/AgentLogger.js +511 -0
- package/src/agents/status/AgentStatusManager.js +481 -0
- package/src/agents/storage/FileManager.js +454 -0
- package/src/agents/verification/AgentCommunicationTester.js +474 -0
- package/src/agents/verification/BaseVerifier.js +430 -0
- package/src/agents/verification/CommandVerifier.js +480 -0
- package/src/agents/verification/FileOperationVerifier.js +453 -0
- package/src/agents/verification/ResultAnalyzer.js +707 -0
- package/src/agents/verification/TestRequirementManager.js +495 -0
- package/src/agents/verification/VerificationRunner.js +433 -0
- package/src/agents/windows/BaseWindowsInstaller.js +441 -0
- package/src/agents/windows/ChocolateyInstaller.js +509 -0
- package/src/agents/windows/DirectInstaller.js +443 -0
- package/src/agents/windows/InstallerFactory.js +391 -0
- package/src/agents/windows/NpmInstaller.js +505 -0
- package/src/agents/windows/PowerShellInstaller.js +458 -0
- package/src/agents/windows/WinGetInstaller.js +390 -0
- package/src/analysis/analysis-reporter.js +132 -0
- package/src/analysis/boundary-detector.js +712 -0
- package/src/analysis/categorizer.js +340 -0
- package/src/analysis/codebase-scanner.js +384 -0
- package/src/analysis/line-counter.js +513 -0
- package/src/analysis/priority-calculator.js +679 -0
- package/src/analysis/report/analysis-report.js +250 -0
- package/src/analysis/report/package-analyzer.js +278 -0
- package/src/analysis/report/recommendation-generator.js +382 -0
- package/src/analysis/report/statistics-generator.js +515 -0
- package/src/analysis/reports/analysis-report-model.js +101 -0
- package/src/analysis/reports/recommendation-generator.js +283 -0
- package/src/analysis/reports/report-generators.js +191 -0
- package/src/analysis/reports/statistics-calculator.js +231 -0
- package/src/analysis/reports/trend-analyzer.js +219 -0
- package/src/analysis/strategy-generator.js +814 -0
- package/src/auto-mode/AutoModeBusinessLogic.js +836 -0
- package/src/config/refactoring-config.js +307 -0
- package/src/health-tracking/json-storage.js +38 -2
- package/src/ide-integration/applescript-manager-core.js +233 -0
- package/src/ide-integration/applescript-manager.cjs +357 -28
- package/src/ide-integration/applescript-manager.js +89 -3599
- package/src/ide-integration/cdp-manager.js +306 -0
- package/src/ide-integration/claude-code-cli-manager.cjs +1 -1
- package/src/ide-integration/continuation-handler.js +337 -0
- package/src/ide-integration/ide-status-checker.js +292 -0
- package/src/ide-integration/macos-ide-manager.js +627 -0
- package/src/ide-integration/macos-text-sender.js +528 -0
- package/src/ide-integration/response-reader.js +548 -0
- package/src/ide-integration/windows-automation-manager.js +121 -0
- package/src/ide-integration/windows-ide-manager.js +373 -0
- package/src/index.cjs +25 -3
- package/src/index.js +15 -1
- package/src/llm/direct-llm-manager.cjs +90 -2
- package/src/models/compliance-report.js +538 -0
- package/src/models/file-analysis.js +681 -0
- package/src/models/refactoring-plan.js +770 -0
- package/src/monitoring/alert-system.js +834 -0
- package/src/monitoring/compliance-progress-tracker.js +437 -0
- package/src/monitoring/continuous-scan-notifications.js +661 -0
- package/src/monitoring/continuous-scanner.js +279 -0
- package/src/monitoring/file-monitor/file-analyzer.js +262 -0
- package/src/monitoring/file-monitor/file-monitor.js +237 -0
- package/src/monitoring/file-monitor/watcher.js +194 -0
- package/src/monitoring/file-monitor.js +17 -0
- package/src/monitoring/notification-manager.js +437 -0
- package/src/monitoring/scanner-core.js +368 -0
- package/src/monitoring/scanner-events.js +214 -0
- package/src/monitoring/violation-notification-system.js +515 -0
- package/src/refactoring/boundaries/cohesion-analyzer.js +316 -0
- package/src/refactoring/boundaries/extraction-result.js +285 -0
- package/src/refactoring/boundaries/extraction-strategies.js +392 -0
- package/src/refactoring/boundaries/module-boundary.js +209 -0
- package/src/refactoring/boundary/boundary-detector.js +741 -0
- package/src/refactoring/boundary/boundary-types.js +405 -0
- package/src/refactoring/boundary/extraction-strategies.js +554 -0
- package/src/refactoring/boundary-extraction-result.js +77 -0
- package/src/refactoring/boundary-extraction-strategies.js +330 -0
- package/src/refactoring/boundary-extractor.js +384 -0
- package/src/refactoring/boundary-types.js +46 -0
- package/src/refactoring/circular/circular-dependency.js +88 -0
- package/src/refactoring/circular/cycle-detection.js +147 -0
- package/src/refactoring/circular/dependency-node.js +82 -0
- package/src/refactoring/circular/dependency-result.js +107 -0
- package/src/refactoring/circular/dependency-types.js +58 -0
- package/src/refactoring/circular/graph-builder.js +213 -0
- package/src/refactoring/circular/resolution-strategy.js +72 -0
- package/src/refactoring/circular/strategy-generator.js +229 -0
- package/src/refactoring/circular-dependency-resolver-original.js +809 -0
- package/src/refactoring/circular-dependency-resolver.js +200 -0
- package/src/refactoring/code-mover.js +761 -0
- package/src/refactoring/file-splitter.js +696 -0
- package/src/refactoring/functionality-validator.js +816 -0
- package/src/refactoring/import-manager.js +774 -0
- package/src/refactoring/module-boundary.js +107 -0
- package/src/refactoring/refactoring-executor.js +672 -0
- package/src/refactoring/refactoring-rollback.js +614 -0
- package/src/refactoring/test-validator.js +631 -0
- package/src/requirement-management/default-requirement-manager.js +321 -0
- package/src/requirement-management/requirement-file-parser.js +159 -0
- package/src/requirement-management/requirement-sequencer.js +221 -0
- package/src/rui/commands/AgentCommandParser.js +600 -0
- package/src/rui/commands/AgentCommands.js +487 -0
- package/src/rui/commands/AgentResponseFormatter.js +832 -0
- package/src/scripts/verify-full-compliance.js +269 -0
- package/src/sync/sync-engine-core.js +1 -0
- package/src/sync/sync-engine-remote-handlers.js +135 -0
- package/src/task-generation/automated-task-generator.js +351 -0
- package/src/task-generation/prioritizer.js +287 -0
- package/src/task-generation/task-list-updater.js +215 -0
- package/src/task-generation/task-management-integration.js +480 -0
- package/src/task-generation/task-manager-integration.js +270 -0
- package/src/task-generation/violation-task-generator.js +474 -0
- package/src/task-management/continuous-scan-integration.js +342 -0
- package/src/timeout-management/index.js +12 -3
- package/src/timeout-management/response-time-tracker.js +167 -0
- package/src/timeout-management/timeout-calculator.js +159 -0
- package/src/timeout-management/timeout-config-manager.js +172 -0
- package/src/utils/ast-analyzer.js +417 -0
- package/src/utils/current-requirement-manager.js +276 -0
- package/src/utils/current-requirement-operations.js +472 -0
- package/src/utils/dependency-mapper.js +456 -0
- package/src/utils/download-with-progress.js +4 -2
- package/src/utils/electron-update-checker.js +4 -1
- package/src/utils/file-size-analyzer.js +272 -0
- package/src/utils/import-updater.js +280 -0
- package/src/utils/refactoring-tools.js +512 -0
- package/src/utils/report-generator.js +569 -0
- package/src/utils/reports/report-analysis.js +218 -0
- package/src/utils/reports/report-types.js +55 -0
- package/src/utils/reports/summary-generators.js +102 -0
- package/src/utils/requirement-file-management.js +157 -0
- package/src/utils/requirement-helpers/requirement-file-ops.js +392 -0
- package/src/utils/requirement-helpers/requirement-mover.js +414 -0
- package/src/utils/requirement-helpers/requirement-parser.js +326 -0
- package/src/utils/requirement-helpers/requirement-status.js +320 -0
- package/src/utils/requirement-helpers-new.js +55 -0
- package/src/utils/requirement-helpers-refactored.js +367 -0
- package/src/utils/requirement-helpers.js +291 -1191
- package/src/utils/requirement-movement-operations.js +450 -0
- package/src/utils/requirement-movement.js +312 -0
- package/src/utils/requirement-parsing-helpers.js +56 -0
- package/src/utils/requirement-statistics.js +200 -0
- package/src/utils/requirement-text-utils.js +58 -0
- package/src/utils/rollback/rollback-handlers.js +125 -0
- package/src/utils/rollback/rollback-operation.js +63 -0
- package/src/utils/rollback/rollback-recorder.js +166 -0
- package/src/utils/rollback/rollback-state-manager.js +175 -0
- package/src/utils/rollback/rollback-types.js +33 -0
- package/src/utils/rollback/rollback-utils.js +110 -0
- package/src/utils/rollback-manager-original.js +569 -0
- package/src/utils/rollback-manager.js +202 -0
- package/src/utils/smoke-test-cli.js +362 -0
- package/src/utils/smoke-test-gui.js +351 -0
- package/src/utils/smoke-test-orchestrator.js +321 -0
- package/src/utils/smoke-test-runner.js +60 -0
- package/src/utils/smoke-test-web.js +347 -0
- package/src/utils/specification-helpers.js +39 -13
- package/src/utils/specification-migration.js +97 -0
- package/src/utils/test-runner.js +579 -0
- package/src/utils/validation-framework.js +518 -0
- package/src/validation/compliance-analyzer.js +197 -0
- package/src/validation/compliance-report-generator.js +343 -0
- package/src/validation/compliance-reporter.js +711 -0
- package/src/validation/compliance-rules.js +127 -0
- package/src/validation/constitution-validator-new.js +196 -0
- package/src/validation/constitution-validator.js +17 -0
- package/src/validation/file-validators.js +170 -0
- package/src/validation/line-limit/file-analyzer.js +201 -0
- package/src/validation/line-limit/line-limit-validator.js +208 -0
- package/src/validation/line-limit/validation-result.js +144 -0
- package/src/validation/line-limit-core.js +225 -0
- package/src/validation/line-limit-reporter.js +134 -0
- package/src/validation/line-limit-result.js +125 -0
- package/src/validation/line-limit-validator.js +41 -0
- package/src/validation/metrics-calculator.js +660 -0
- 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
|
-
|
|
6
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
25
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
728
|
-
*
|
|
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
|
|
732
|
-
|
|
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
|
-
*
|
|
890
|
-
*
|
|
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
|
|
894
|
-
|
|
895
|
-
|
|
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
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1171
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1327
|
-
|
|
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
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2030
|
-
|
|
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'
|
|
214
|
+
* @param {string} ide - The IDE name ('windsurf')
|
|
2604
215
|
* @returns {Promise<Object>} Quota detection result
|
|
2605
216
|
*/
|
|
2606
217
|
async detectQuotaWarning(ide) {
|
|
2607
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
3544
|
-
*
|
|
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
|
|
3547
|
-
|
|
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
|
|
3585
|
-
* @
|
|
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
|
|
3670
|
-
|
|
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
|
-
*
|
|
3718
|
-
* @
|
|
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
|
|
3721
|
-
return this.
|
|
280
|
+
async clickContinuationButton(ide) {
|
|
281
|
+
return await this.coreManager.clickContinuationButton(ide);
|
|
3722
282
|
}
|
|
3723
283
|
|
|
3724
284
|
/**
|
|
3725
|
-
* Check
|
|
3726
|
-
* @
|
|
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
|
-
|
|
3729
|
-
|
|
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
|
-
*
|
|
3778
|
-
* @
|
|
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
|
|
3781
|
-
|
|
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
|
-
|
|
302
|
+
export { AppleScriptManager };
|