vibecodingmachine-core 2025.12.25-25 → 2026.1.22-1441
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/ERROR_REPORTING_API.md +212 -0
- package/ERROR_REPORTING_USAGE.md +380 -0
- package/__tests__/provider-manager-fallback.test.js +43 -0
- package/__tests__/provider-manager-rate-limit.test.js +61 -0
- package/__tests__/utils/git-branch-manager.test.js +61 -0
- package/package.json +1 -1
- package/src/beta-request.js +160 -0
- package/src/compliance/compliance-manager.js +5 -2
- package/src/database/migrations.js +135 -12
- package/src/database/user-database-client.js +127 -8
- package/src/database/user-schema.js +28 -0
- package/src/health-tracking/__tests__/ide-health-tracker.test.js +420 -0
- package/src/health-tracking/__tests__/interaction-recorder.test.js +392 -0
- package/src/health-tracking/errors.js +50 -0
- package/src/health-tracking/health-reporter.js +331 -0
- package/src/health-tracking/ide-health-tracker.js +446 -0
- package/src/health-tracking/interaction-recorder.js +161 -0
- package/src/health-tracking/json-storage.js +276 -0
- package/src/health-tracking/storage-interface.js +63 -0
- package/src/health-tracking/validators.js +277 -0
- package/src/ide-integration/applescript-manager.cjs +1087 -9
- package/src/ide-integration/applescript-manager.js +565 -15
- package/src/ide-integration/applescript-utils.js +26 -18
- package/src/ide-integration/provider-manager.cjs +158 -28
- package/src/ide-integration/quota-detector.cjs +339 -16
- package/src/ide-integration/quota-detector.js +6 -1
- package/src/index.cjs +36 -1
- package/src/index.js +20 -0
- package/src/localization/translations/en.js +15 -1
- package/src/localization/translations/es.js +14 -0
- package/src/requirement-numbering.js +164 -0
- package/src/sync/aws-setup.js +4 -4
- package/src/utils/admin-utils.js +33 -0
- package/src/utils/error-reporter.js +117 -0
- package/src/utils/git-branch-manager.js +278 -0
- package/src/utils/requirement-helpers.js +44 -5
- package/src/utils/requirements-parser.js +28 -3
- package/tests/health-tracking/health-reporter.test.js +329 -0
- package/tests/health-tracking/ide-health-tracker.test.js +368 -0
- package/tests/health-tracking/interaction-recorder.test.js +309 -0
|
@@ -13,6 +13,589 @@ class AppleScriptManager {
|
|
|
13
13
|
this.logger = console;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Best-effort: switch Antigravity's active model to the requested label.
|
|
18
|
+
* @param {string} modelName
|
|
19
|
+
* @returns {Promise<{success: boolean, model?: string, error?: string}>}
|
|
20
|
+
*/
|
|
21
|
+
async switchAntigravityModel(modelName) {
|
|
22
|
+
try {
|
|
23
|
+
if (!modelName || typeof modelName !== 'string') {
|
|
24
|
+
return { success: false, error: 'Invalid model name' };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const script = `
|
|
28
|
+
tell application "System Events"
|
|
29
|
+
tell process "Antigravity"
|
|
30
|
+
set frontmost to true
|
|
31
|
+
delay 0.8
|
|
32
|
+
|
|
33
|
+
try
|
|
34
|
+
set buttonTexts to {"Select another model", "Switch model", "Change model", "Select model", "Try another model", "Choose model"}
|
|
35
|
+
set foundButton to missing value
|
|
36
|
+
|
|
37
|
+
repeat with buttonText in buttonTexts
|
|
38
|
+
try
|
|
39
|
+
set matchingButtons to buttons of window 1 whose name contains buttonText
|
|
40
|
+
if (count of matchingButtons) > 0 then
|
|
41
|
+
set foundButton to item 1 of matchingButtons
|
|
42
|
+
exit repeat
|
|
43
|
+
end if
|
|
44
|
+
end try
|
|
45
|
+
end repeat
|
|
46
|
+
|
|
47
|
+
if foundButton is missing value then
|
|
48
|
+
repeat with buttonText in buttonTexts
|
|
49
|
+
try
|
|
50
|
+
set allGroups to groups of window 1
|
|
51
|
+
repeat with grp in allGroups
|
|
52
|
+
try
|
|
53
|
+
set matchingButtons to buttons of grp whose name contains buttonText
|
|
54
|
+
if (count of matchingButtons) > 0 then
|
|
55
|
+
set foundButton to item 1 of matchingButtons
|
|
56
|
+
exit repeat
|
|
57
|
+
end if
|
|
58
|
+
end try
|
|
59
|
+
end repeat
|
|
60
|
+
if foundButton is not missing value then exit repeat
|
|
61
|
+
end try
|
|
62
|
+
end repeat
|
|
63
|
+
end if
|
|
64
|
+
|
|
65
|
+
if foundButton is missing value then
|
|
66
|
+
return "error:button-not-found"
|
|
67
|
+
end if
|
|
68
|
+
|
|
69
|
+
click foundButton
|
|
70
|
+
delay 1.2
|
|
71
|
+
|
|
72
|
+
set modelItems to {}
|
|
73
|
+
try
|
|
74
|
+
set modelItems to menu items of menu 1 of window 1 whose name contains "${modelName.replace(/"/g, '\\"')}"
|
|
75
|
+
end try
|
|
76
|
+
|
|
77
|
+
if (count of modelItems) = 0 then
|
|
78
|
+
try
|
|
79
|
+
set modelItems to buttons of window 1 whose name contains "${modelName.replace(/"/g, '\\"')}"
|
|
80
|
+
end try
|
|
81
|
+
end if
|
|
82
|
+
|
|
83
|
+
if (count of modelItems) = 0 then
|
|
84
|
+
try
|
|
85
|
+
set allGroups to groups of window 1
|
|
86
|
+
repeat with grp in allGroups
|
|
87
|
+
try
|
|
88
|
+
set matchingItems to (every UI element of grp whose name contains "${modelName.replace(/"/g, '\\"')}")
|
|
89
|
+
if (count of matchingItems) > 0 then
|
|
90
|
+
set modelItems to matchingItems
|
|
91
|
+
exit repeat
|
|
92
|
+
end if
|
|
93
|
+
end try
|
|
94
|
+
end repeat
|
|
95
|
+
end try
|
|
96
|
+
end if
|
|
97
|
+
|
|
98
|
+
if (count of modelItems) = 0 then
|
|
99
|
+
return "error:model-not-found"
|
|
100
|
+
end if
|
|
101
|
+
|
|
102
|
+
click item 1 of modelItems
|
|
103
|
+
delay 0.6
|
|
104
|
+
return "success:${modelName.replace(/"/g, '\\"')}"
|
|
105
|
+
on error errMsg
|
|
106
|
+
return "error:" & errMsg
|
|
107
|
+
end try
|
|
108
|
+
end tell
|
|
109
|
+
end tell
|
|
110
|
+
`;
|
|
111
|
+
|
|
112
|
+
const result = execSync(`osascript -e '${script.replace(/'/g, "'\\''")}'`, {
|
|
113
|
+
encoding: 'utf8',
|
|
114
|
+
timeout: 15000
|
|
115
|
+
}).trim();
|
|
116
|
+
|
|
117
|
+
if (result.startsWith('success:')) {
|
|
118
|
+
return { success: true, model: result.substring(8) };
|
|
119
|
+
}
|
|
120
|
+
return { success: false, error: result };
|
|
121
|
+
} catch (error) {
|
|
122
|
+
return { success: false, error: error.message };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Check Antigravity UI for Gemini quota limit popup text.
|
|
128
|
+
* This is best-effort: Antigravity may render the 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
|
+
try {
|
|
133
|
+
const script = `
|
|
134
|
+
tell application "System Events"
|
|
135
|
+
if not (exists process "Antigravity") then
|
|
136
|
+
return "NOT_RUNNING"
|
|
137
|
+
end if
|
|
138
|
+
tell process "Antigravity"
|
|
139
|
+
set frontmost to true
|
|
140
|
+
delay 0.4
|
|
141
|
+
|
|
142
|
+
set allText to ""
|
|
143
|
+
try
|
|
144
|
+
set allText to allText & (value of static text of window 1 as string) & "\n"
|
|
145
|
+
end try
|
|
146
|
+
|
|
147
|
+
try
|
|
148
|
+
set groupsList to groups of window 1
|
|
149
|
+
repeat with g in groupsList
|
|
150
|
+
try
|
|
151
|
+
set allText to allText & (value of static text of g as string) & "\n"
|
|
152
|
+
end try
|
|
153
|
+
end repeat
|
|
154
|
+
end try
|
|
155
|
+
|
|
156
|
+
return allText
|
|
157
|
+
end tell
|
|
158
|
+
end tell
|
|
159
|
+
`;
|
|
160
|
+
|
|
161
|
+
const raw = execSync(`osascript -e '${script.replace(/'/g, "'\\''")}'`, {
|
|
162
|
+
encoding: 'utf8',
|
|
163
|
+
timeout: 8000
|
|
164
|
+
}).trim();
|
|
165
|
+
|
|
166
|
+
if (!raw || raw === 'NOT_RUNNING') {
|
|
167
|
+
return {
|
|
168
|
+
isRateLimited: false,
|
|
169
|
+
note: raw === 'NOT_RUNNING' ? 'Antigravity not running' : 'No readable UI text'
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const text = raw.replace(/\s+/g, ' ').trim();
|
|
174
|
+
const lower = text.toLowerCase();
|
|
175
|
+
|
|
176
|
+
const hasGeminiQuota = lower.includes('model quota limit exceeded') ||
|
|
177
|
+
(lower.includes('quota limit') && lower.includes('you can resume')) ||
|
|
178
|
+
lower.includes('you have reached the quota limit') ||
|
|
179
|
+
lower.includes('spending cap reached') ||
|
|
180
|
+
lower.includes('usage cap reached') ||
|
|
181
|
+
lower.includes('rate limit reached');
|
|
182
|
+
|
|
183
|
+
if (!hasGeminiQuota) {
|
|
184
|
+
return { isRateLimited: false };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
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);
|
|
188
|
+
const resumeAt = resumeAtMatch
|
|
189
|
+
? `${resumeAtMatch[1]}, ${resumeAtMatch[2]} ${resumeAtMatch[3].toUpperCase()}`
|
|
190
|
+
: undefined;
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
isRateLimited: true,
|
|
194
|
+
message: text,
|
|
195
|
+
resumeAt
|
|
196
|
+
};
|
|
197
|
+
} catch (error) {
|
|
198
|
+
return {
|
|
199
|
+
isRateLimited: false,
|
|
200
|
+
note: `Failed to check Antigravity quota: ${error.message}`
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Check AWS Kiro UI for quota limit messages
|
|
207
|
+
* This is best-effort: Kiro may render quota messages in different UI elements or web-based UI
|
|
208
|
+
* @returns {Promise<{isRateLimited: boolean, message?: string, resumeAt?: string, note?: string}>}
|
|
209
|
+
*/
|
|
210
|
+
async checkKiroQuotaLimit() {
|
|
211
|
+
try {
|
|
212
|
+
// First try AppleScript UI detection
|
|
213
|
+
const script = `
|
|
214
|
+
tell application "System Events"
|
|
215
|
+
set processName to "AWS Kiro"
|
|
216
|
+
try
|
|
217
|
+
if not (exists process "AWS Kiro") then
|
|
218
|
+
set processName to "Kiro"
|
|
219
|
+
end if
|
|
220
|
+
on error
|
|
221
|
+
set processName to "Kiro"
|
|
222
|
+
end try
|
|
223
|
+
|
|
224
|
+
if not (exists process processName) then
|
|
225
|
+
return "NOT_RUNNING"
|
|
226
|
+
end if
|
|
227
|
+
|
|
228
|
+
tell process processName
|
|
229
|
+
set frontmost to true
|
|
230
|
+
delay 0.4
|
|
231
|
+
|
|
232
|
+
set allText to ""
|
|
233
|
+
|
|
234
|
+
-- Get window title (might contain quota info)
|
|
235
|
+
try
|
|
236
|
+
set windowTitle to title of window 1
|
|
237
|
+
set allText to allText & "WINDOW_TITLE: " & windowTitle & "\\n"
|
|
238
|
+
end try
|
|
239
|
+
|
|
240
|
+
-- Get window description
|
|
241
|
+
try
|
|
242
|
+
set windowDesc to description of window 1
|
|
243
|
+
set allText to allText & "WINDOW_DESC: " & windowDesc & "\\n"
|
|
244
|
+
end try
|
|
245
|
+
|
|
246
|
+
-- Get window help text
|
|
247
|
+
try
|
|
248
|
+
set windowHelp to help of window 1
|
|
249
|
+
set allText to allText & "WINDOW_HELP: " & windowHelp & "\\n"
|
|
250
|
+
end try
|
|
251
|
+
|
|
252
|
+
-- Try to get text from main window
|
|
253
|
+
try
|
|
254
|
+
set windowText to (value of static text of window 1 as string)
|
|
255
|
+
set allText to allText & "WINDOW_STATIC: " & windowText & "\\n"
|
|
256
|
+
end try
|
|
257
|
+
|
|
258
|
+
-- Try to get text from groups in main window
|
|
259
|
+
try
|
|
260
|
+
set groupsList to groups of window 1
|
|
261
|
+
repeat with g in groupsList
|
|
262
|
+
try
|
|
263
|
+
set groupText to (value of static text of g as string)
|
|
264
|
+
set allText to allText & "GROUP: " & groupText & "\\n"
|
|
265
|
+
end try
|
|
266
|
+
end repeat
|
|
267
|
+
end try
|
|
268
|
+
|
|
269
|
+
-- Try to get text from buttons in main window
|
|
270
|
+
try
|
|
271
|
+
set buttonsList to buttons of window 1
|
|
272
|
+
repeat with b in buttonsList
|
|
273
|
+
try
|
|
274
|
+
set buttonText to (name of b as string)
|
|
275
|
+
set allText to allText & "BUTTON: " & buttonText & "\\n"
|
|
276
|
+
end try
|
|
277
|
+
end repeat
|
|
278
|
+
end try
|
|
279
|
+
|
|
280
|
+
-- Try to get text from text fields in main window
|
|
281
|
+
try
|
|
282
|
+
set textFieldsList to text fields of window 1
|
|
283
|
+
repeat with tf in textFieldsList
|
|
284
|
+
try
|
|
285
|
+
set fieldText to (value of tf as string)
|
|
286
|
+
set allText to allText & "TEXT_FIELD: " & fieldText & "\\n"
|
|
287
|
+
end try
|
|
288
|
+
end repeat
|
|
289
|
+
end try
|
|
290
|
+
|
|
291
|
+
-- Try to get text from text views in main window
|
|
292
|
+
try
|
|
293
|
+
set textViewsList to text views of window 1
|
|
294
|
+
repeat with tv in textViewsList
|
|
295
|
+
try
|
|
296
|
+
set viewText to (value of tv as string)
|
|
297
|
+
set allText to allText & "TEXT_VIEW: " & viewText & "\\n"
|
|
298
|
+
end try
|
|
299
|
+
end repeat
|
|
300
|
+
end try
|
|
301
|
+
|
|
302
|
+
-- Try to get text from sheets (dialogs/alerts)
|
|
303
|
+
try
|
|
304
|
+
set sheetsList to sheets of window 1
|
|
305
|
+
repeat with s in sheetsList
|
|
306
|
+
try
|
|
307
|
+
set sheetText to (value of static text of s as string)
|
|
308
|
+
set allText to allText & "SHEET: " & sheetText & "\\n"
|
|
309
|
+
end try
|
|
310
|
+
end repeat
|
|
311
|
+
end try
|
|
312
|
+
|
|
313
|
+
-- Try to get text from all UI elements (catch-all)
|
|
314
|
+
try
|
|
315
|
+
set uiElementsList to every UI element of window 1
|
|
316
|
+
repeat with element in uiElementsList
|
|
317
|
+
try
|
|
318
|
+
set elementText to value of element as string
|
|
319
|
+
if elementText is not missing value and elementText is not "" then
|
|
320
|
+
set allText to allText & "UI_ELEMENT: " & elementText & "\\n"
|
|
321
|
+
end if
|
|
322
|
+
end try
|
|
323
|
+
end repeat
|
|
324
|
+
end try
|
|
325
|
+
|
|
326
|
+
-- Try to get accessibility attributes
|
|
327
|
+
try
|
|
328
|
+
set windowRole to role of window 1
|
|
329
|
+
set allText to allText & "WINDOW_ROLE: " & windowRole & "\\n"
|
|
330
|
+
end try
|
|
331
|
+
|
|
332
|
+
try
|
|
333
|
+
set windowSubrole to subrole of window 1
|
|
334
|
+
set allText to allText & "WINDOW_SUBROLE: " & windowSubrole & "\\n"
|
|
335
|
+
end try
|
|
336
|
+
|
|
337
|
+
return allText
|
|
338
|
+
end tell
|
|
339
|
+
end tell
|
|
340
|
+
`;
|
|
341
|
+
|
|
342
|
+
const raw = execSync(`osascript -e '${script.replace(/'/g, "'\\''")}'`, {
|
|
343
|
+
encoding: 'utf8',
|
|
344
|
+
timeout: 8000
|
|
345
|
+
}).trim();
|
|
346
|
+
|
|
347
|
+
if (!raw || raw === 'NOT_RUNNING') {
|
|
348
|
+
return {
|
|
349
|
+
isRateLimited: false,
|
|
350
|
+
note: raw === 'NOT_RUNNING' ? 'AWS Kiro not running' : 'No readable UI text'
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const text = raw.replace(/\s+/g, ' ').trim();
|
|
355
|
+
const lower = text.toLowerCase();
|
|
356
|
+
|
|
357
|
+
// Check for Kiro-specific quota limit patterns (expanded for better detection)
|
|
358
|
+
const hasKiroQuota = lower.includes('quota limit exceeded') ||
|
|
359
|
+
lower.includes('usage limit reached') ||
|
|
360
|
+
lower.includes('rate limit exceeded') ||
|
|
361
|
+
lower.includes('api limit exceeded') ||
|
|
362
|
+
lower.includes('request limit exceeded') ||
|
|
363
|
+
lower.includes('too many requests') ||
|
|
364
|
+
lower.includes('quota exceeded') ||
|
|
365
|
+
lower.includes('limit exceeded') ||
|
|
366
|
+
lower.includes('usage exceeded') ||
|
|
367
|
+
lower.includes('rate limited') ||
|
|
368
|
+
lower.includes('api limited') ||
|
|
369
|
+
lower.includes('request limited') ||
|
|
370
|
+
lower.includes('quota reached') ||
|
|
371
|
+
lower.includes('limit reached') ||
|
|
372
|
+
lower.includes('usage reached') ||
|
|
373
|
+
lower.includes('cap reached') ||
|
|
374
|
+
lower.includes('cap exceeded') ||
|
|
375
|
+
lower.includes('spending cap') ||
|
|
376
|
+
lower.includes('usage cap') ||
|
|
377
|
+
lower.includes('daily limit') ||
|
|
378
|
+
lower.includes('monthly limit') ||
|
|
379
|
+
lower.includes('hourly limit') ||
|
|
380
|
+
lower.includes('request limit') ||
|
|
381
|
+
lower.includes('token limit') ||
|
|
382
|
+
lower.includes('credit limit') ||
|
|
383
|
+
lower.includes('billing limit') ||
|
|
384
|
+
lower.includes('subscription limit') ||
|
|
385
|
+
lower.includes('plan limit') ||
|
|
386
|
+
lower.includes('tier limit') ||
|
|
387
|
+
lower.includes('upgrade required') ||
|
|
388
|
+
lower.includes('upgrade needed') ||
|
|
389
|
+
lower.includes('upgrade to pro') ||
|
|
390
|
+
lower.includes('upgrade to premium') ||
|
|
391
|
+
lower.includes('upgrade plan') ||
|
|
392
|
+
lower.includes('increase limit') ||
|
|
393
|
+
lower.includes('exhausted') ||
|
|
394
|
+
lower.includes('no more requests') ||
|
|
395
|
+
lower.includes('no more credits') ||
|
|
396
|
+
lower.includes('insufficient credits') ||
|
|
397
|
+
lower.includes('insufficient quota') ||
|
|
398
|
+
lower.includes('maximum reached') ||
|
|
399
|
+
lower.includes('max requests') ||
|
|
400
|
+
lower.includes('max usage') ||
|
|
401
|
+
lower.includes('over limit') ||
|
|
402
|
+
lower.includes('over quota') ||
|
|
403
|
+
lower.includes('over usage') ||
|
|
404
|
+
lower.includes('out of credits') ||
|
|
405
|
+
lower.includes('out of credit') ||
|
|
406
|
+
lower.includes('credits exhausted') ||
|
|
407
|
+
lower.includes('credits depleted') ||
|
|
408
|
+
lower.includes('credits used up') ||
|
|
409
|
+
lower.includes('credit exhausted') ||
|
|
410
|
+
lower.includes('credit depleted') ||
|
|
411
|
+
lower.includes('credit used up') ||
|
|
412
|
+
lower.includes('credit balance is too low') ||
|
|
413
|
+
lower.includes('not enough credits') ||
|
|
414
|
+
lower.includes('credits: 0') ||
|
|
415
|
+
lower.includes('credit balance: 0') ||
|
|
416
|
+
lower.includes('no credits available') ||
|
|
417
|
+
lower.includes('purchase more credits') ||
|
|
418
|
+
lower.includes('credits: 0 remaining') ||
|
|
419
|
+
lower.includes('credit balance: 0') ||
|
|
420
|
+
(lower.includes('credits') && (lower.includes('out of') || lower.includes('exhausted') || lower.includes('depleted') || lower.includes('used up'))) ||
|
|
421
|
+
(lower.includes('credit') && (lower.includes('out of') || lower.includes('exhausted') || lower.includes('depleted') || lower.includes('used up') || lower.includes('balance is too low') || lower.includes('balance: 0'))) ||
|
|
422
|
+
(lower.includes('quota') && (lower.includes('exceeded') || lower.includes('reached') || lower.includes('exhaust'))) ||
|
|
423
|
+
(lower.includes('limit') && (lower.includes('exceeded') || lower.includes('reached') || lower.includes('exhaust'))) ||
|
|
424
|
+
(lower.includes('cap') && (lower.includes('exceeded') || lower.includes('reached') || lower.includes('exhaust')));
|
|
425
|
+
|
|
426
|
+
if (hasKiroQuota) {
|
|
427
|
+
// Try to extract reset time from various formats (expanded for better detection)
|
|
428
|
+
const resumeAtMatch = text.match(/(?:resume|reset|available|renew|refresh|restart|continue|try again)\s+(?:at|in|on)\s+(\d{1,2}\/\d{1,2}\/\d{4})\s*,?\s*(\d{1,2}:\d{2}(?::\d{2})?)\s*(am|pm)/i);
|
|
429
|
+
const resumeAt = resumeAtMatch
|
|
430
|
+
? `${resumeAtMatch[1]}, ${resumeAtMatch[2]} ${resumeAtMatch[3].toUpperCase()}`
|
|
431
|
+
: undefined;
|
|
432
|
+
|
|
433
|
+
// Also try other time formats
|
|
434
|
+
if (!resumeAt) {
|
|
435
|
+
const timeMatch = text.match(/(?:try again|resume|reset|available|renew|refresh|restart|continue)\s+(?:in|after)\s+(\d+)\s*(?:minutes?|hours?|days?|seconds?)/i);
|
|
436
|
+
if (timeMatch) {
|
|
437
|
+
// For "in X minutes/hours/days" format, we'll return the raw text
|
|
438
|
+
return {
|
|
439
|
+
isRateLimited: true,
|
|
440
|
+
message: text,
|
|
441
|
+
resumeTime: timeMatch[1] + ' ' + timeMatch[2]
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Try specific time patterns like "resets at 12pm" or "resets at 3:30 PM"
|
|
447
|
+
if (!resumeAt) {
|
|
448
|
+
const timeOnlyMatch = text.match(/(?:resets|reset|resume|available)\s+(?:at|@)\s+(\d{1,2}:\d{2}(?::\d{2})?)\s*(am|pm)?/i);
|
|
449
|
+
if (timeOnlyMatch) {
|
|
450
|
+
const time = timeOnlyMatch[1] + (timeOnlyMatch[2] ? ' ' + timeOnlyMatch[2].toUpperCase() : '');
|
|
451
|
+
return {
|
|
452
|
+
isRateLimited: true,
|
|
453
|
+
message: text,
|
|
454
|
+
resumeTime: time
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Try date patterns like "resets on January 15" or "resets on 1/15"
|
|
460
|
+
if (!resumeAt) {
|
|
461
|
+
const dateMatch = text.match(/(?:resets|reset|resume|available)\s+(?:on|at)\s+(\d{1,2}\/\d{1,2})(?:\/\d{4})?/i);
|
|
462
|
+
if (dateMatch) {
|
|
463
|
+
return {
|
|
464
|
+
isRateLimited: true,
|
|
465
|
+
message: text,
|
|
466
|
+
resumeTime: dateMatch[1]
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return {
|
|
472
|
+
isRateLimited: true,
|
|
473
|
+
message: text,
|
|
474
|
+
resumeAt
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// If AppleScript didn't find quota messages, try fallback methods
|
|
479
|
+
return await this.checkKiroQuotaLimitFallback();
|
|
480
|
+
|
|
481
|
+
} catch (error) {
|
|
482
|
+
return {
|
|
483
|
+
isRateLimited: false,
|
|
484
|
+
note: `Failed to check AWS Kiro quota: ${error.message}`
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Fallback method to check Kiro quota using alternative approaches
|
|
491
|
+
* @returns {Promise<{isRateLimited: boolean, message?: string, note?: string}>}
|
|
492
|
+
*/
|
|
493
|
+
async checkKiroQuotaLimitFallback() {
|
|
494
|
+
try {
|
|
495
|
+
// Method 1: Check if Kiro process is running and consuming high CPU (might indicate quota error)
|
|
496
|
+
const processCheck = execSync('ps aux | grep -i kiro | grep -v grep', {
|
|
497
|
+
encoding: 'utf8',
|
|
498
|
+
timeout: 3000
|
|
499
|
+
}).trim();
|
|
500
|
+
|
|
501
|
+
if (!processCheck) {
|
|
502
|
+
return {
|
|
503
|
+
isRateLimited: false,
|
|
504
|
+
note: 'AWS Kiro process not found'
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Method 2: Check for system notifications (macOS notifications about quota)
|
|
509
|
+
try {
|
|
510
|
+
const notifications = execSync('log show --predicate \'subsystem == "com.apple.notificationcenter"\' --info --last 5m | grep -i "kiro\\|quota\\|limit"', {
|
|
511
|
+
encoding: 'utf8',
|
|
512
|
+
timeout: 5000
|
|
513
|
+
}).trim();
|
|
514
|
+
|
|
515
|
+
if (notifications) {
|
|
516
|
+
const lower = notifications.toLowerCase();
|
|
517
|
+
if (lower.includes('quota') || lower.includes('limit') || lower.includes('exceeded')) {
|
|
518
|
+
return {
|
|
519
|
+
isRateLimited: true,
|
|
520
|
+
message: 'Quota limit detected in system notifications',
|
|
521
|
+
note: 'Detected via system notifications'
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
} catch (notifError) {
|
|
526
|
+
// Notifications check failed, continue
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Method 3: Check console logs for Kiro-related quota messages
|
|
530
|
+
try {
|
|
531
|
+
const consoleLogs = execSync('log show --predicate \'process == "Kiro" OR process == "AWS Kiro"\' --info --last 10m | grep -i "quota\\|limit\\|exceeded\\|error"', {
|
|
532
|
+
encoding: 'utf8',
|
|
533
|
+
timeout: 5000
|
|
534
|
+
}).trim();
|
|
535
|
+
|
|
536
|
+
if (consoleLogs) {
|
|
537
|
+
const lower = consoleLogs.toLowerCase();
|
|
538
|
+
if (lower.includes('quota') || lower.includes('limit') || lower.includes('exceeded')) {
|
|
539
|
+
return {
|
|
540
|
+
isRateLimited: true,
|
|
541
|
+
message: 'Quota limit detected in console logs',
|
|
542
|
+
note: 'Detected via console logs'
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
} catch (logError) {
|
|
547
|
+
// Console log check failed, continue
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Method 4: Check if Kiro window title contains quota information
|
|
551
|
+
try {
|
|
552
|
+
const windowTitle = execSync(`osascript -e '
|
|
553
|
+
tell application "System Events"
|
|
554
|
+
set processName to "AWS Kiro"
|
|
555
|
+
try
|
|
556
|
+
if not (exists process "AWS Kiro") then
|
|
557
|
+
set processName to "Kiro"
|
|
558
|
+
end if
|
|
559
|
+
on error
|
|
560
|
+
set processName to "Kiro"
|
|
561
|
+
end try
|
|
562
|
+
|
|
563
|
+
if exists process processName then
|
|
564
|
+
return title of window 1 of process processName
|
|
565
|
+
end if
|
|
566
|
+
end tell
|
|
567
|
+
'`, {
|
|
568
|
+
encoding: 'utf8',
|
|
569
|
+
timeout: 3000
|
|
570
|
+
}).trim();
|
|
571
|
+
|
|
572
|
+
if (windowTitle) {
|
|
573
|
+
const lower = windowTitle.toLowerCase();
|
|
574
|
+
if (lower.includes('quota') || lower.includes('limit') || lower.includes('exceeded')) {
|
|
575
|
+
return {
|
|
576
|
+
isRateLimited: true,
|
|
577
|
+
message: `Quota limit detected in window title: ${windowTitle}`,
|
|
578
|
+
note: 'Detected via window title'
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
} catch (titleError) {
|
|
583
|
+
// Window title check failed, continue
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return {
|
|
587
|
+
isRateLimited: false,
|
|
588
|
+
note: 'No quota limit detected via fallback methods'
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
} catch (error) {
|
|
592
|
+
return {
|
|
593
|
+
isRateLimited: false,
|
|
594
|
+
note: `Fallback quota check failed: ${error.message}`
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
16
599
|
/**
|
|
17
600
|
* Open Cursor IDE
|
|
18
601
|
* @returns {Promise<Object>} Result object with success status and details
|
|
@@ -169,6 +752,170 @@ class AppleScriptManager {
|
|
|
169
752
|
}
|
|
170
753
|
}
|
|
171
754
|
|
|
755
|
+
/**
|
|
756
|
+
* Open VS Code with optional repository path
|
|
757
|
+
* @param {string} repoPath - Optional repository path to open
|
|
758
|
+
* @returns {Promise<Object>} Result object with success status and details
|
|
759
|
+
*/
|
|
760
|
+
async openVSCode(repoPath = null) {
|
|
761
|
+
try {
|
|
762
|
+
this.logger.log('Opening VS Code with remote debugging enabled...');
|
|
763
|
+
|
|
764
|
+
// First, check if VS Code is already running
|
|
765
|
+
try {
|
|
766
|
+
const isRunning = execSync('pgrep -x "Code"', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
767
|
+
if (isRunning) {
|
|
768
|
+
this.logger.log('VS Code is already running - closing it to enable remote debugging...');
|
|
769
|
+
execSync('pkill -x "Code"', { stdio: 'pipe' });
|
|
770
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
771
|
+
}
|
|
772
|
+
} catch (err) {
|
|
773
|
+
// VS Code not running, that's fine
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Launch VS Code with remote debugging enabled for quota detection
|
|
777
|
+
let command = 'open -a "Visual Studio Code" --args --remote-debugging-port=9222';
|
|
778
|
+
if (repoPath) {
|
|
779
|
+
command = `open -a "Visual Studio Code" "${repoPath}" --args --remote-debugging-port=9222`;
|
|
780
|
+
this.logger.log(`Opening VS Code with repository: ${repoPath}`);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
execSync(command, { stdio: 'pipe' });
|
|
784
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
785
|
+
|
|
786
|
+
// Verify remote debugging port is accessible
|
|
787
|
+
try {
|
|
788
|
+
const CDP = require('chrome-remote-interface');
|
|
789
|
+
await CDP.List({ port: 9222 });
|
|
790
|
+
this.logger.log('✅ VS Code remote debugging port 9222 is accessible');
|
|
791
|
+
} catch (cdpError) {
|
|
792
|
+
this.logger.log(`⚠️ Warning: VS Code remote debugging port not accessible: ${cdpError.message}`);
|
|
793
|
+
this.logger.log(' Waiting additional 2 seconds for VS Code to fully start...');
|
|
794
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
this.logger.log('VS Code opened successfully with remote debugging');
|
|
798
|
+
return { success: true, message: `VS Code opened with repository: ${repoPath}`, method: 'applescript' };
|
|
799
|
+
} catch (error) {
|
|
800
|
+
this.logger.log('Error opening VS Code:', error.message);
|
|
801
|
+
return { success: false, error: error.message, method: 'applescript' };
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Open VS Code with a specific extension installed
|
|
807
|
+
* @param {string} extension - Extension key (e.g., 'github-copilot', 'amazon-q')
|
|
808
|
+
* @param {string} repoPath - Optional repository path to open
|
|
809
|
+
* @returns {Promise<Object>} Result object with success status and details
|
|
810
|
+
*/
|
|
811
|
+
async openVSCodeWithExtension(extension, repoPath = null) {
|
|
812
|
+
try {
|
|
813
|
+
this.logger.log(`Opening VS Code with ${extension} extension...`);
|
|
814
|
+
|
|
815
|
+
// Extension IDs mapping
|
|
816
|
+
const extensionIds = {
|
|
817
|
+
'github-copilot': 'GitHub.copilot',
|
|
818
|
+
'amazon-q': 'amazonwebservices.amazon-q-vscode',
|
|
819
|
+
'continue': 'Continue.continue'
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
const extensionId = extensionIds[extension];
|
|
823
|
+
if (!extensionId) {
|
|
824
|
+
return { success: false, error: `Unknown extension: ${extension}`, method: 'applescript' };
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// First, check if the extension is installed
|
|
828
|
+
try {
|
|
829
|
+
const checkCmd = `code --list-extensions | grep -i "${extensionId}"`;
|
|
830
|
+
execSync(checkCmd, { stdio: 'pipe' });
|
|
831
|
+
this.logger.log(`Extension ${extension} is already installed`);
|
|
832
|
+
} catch (checkError) {
|
|
833
|
+
// Extension not found, try to install it
|
|
834
|
+
this.logger.log(`Extension ${extension} not found, installing...`);
|
|
835
|
+
try {
|
|
836
|
+
execSync(`code --install-extension ${extensionId} --force`, { stdio: 'pipe', timeout: 60000 });
|
|
837
|
+
this.logger.log(`Extension ${extension} installed successfully`);
|
|
838
|
+
} catch (installError) {
|
|
839
|
+
this.logger.log(`Failed to install extension ${extension}:`, installError.message);
|
|
840
|
+
return { success: false, error: `Failed to install extension: ${installError.message}`, method: 'applescript' };
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// First, check if VS Code is already running
|
|
845
|
+
try {
|
|
846
|
+
const isRunning = execSync('pgrep -x "Code"', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
847
|
+
if (isRunning) {
|
|
848
|
+
this.logger.log('VS Code is already running - closing it to enable remote debugging...');
|
|
849
|
+
execSync('pkill -x "Code"', { stdio: 'pipe' });
|
|
850
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
851
|
+
}
|
|
852
|
+
} catch (err) {
|
|
853
|
+
// VS Code not running, that's fine
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Open VS Code with the repository and remote debugging enabled
|
|
857
|
+
let command = 'open -a "Visual Studio Code" --args --remote-debugging-port=9222';
|
|
858
|
+
if (repoPath) {
|
|
859
|
+
command = `open -a "Visual Studio Code" "${repoPath}" --args --remote-debugging-port=9222`;
|
|
860
|
+
this.logger.log(`Opening VS Code with repository: ${repoPath}`);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
execSync(command, { stdio: 'pipe' });
|
|
864
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
865
|
+
|
|
866
|
+
// Verify remote debugging port is accessible
|
|
867
|
+
try {
|
|
868
|
+
const CDP = require('chrome-remote-interface');
|
|
869
|
+
await CDP.List({ port: 9222 });
|
|
870
|
+
this.logger.log('✅ VS Code remote debugging port 9222 is accessible');
|
|
871
|
+
} catch (cdpError) {
|
|
872
|
+
this.logger.log(`⚠️ Warning: VS Code remote debugging port not accessible: ${cdpError.message}`);
|
|
873
|
+
this.logger.log(' Waiting additional 2 seconds for VS Code to fully start...');
|
|
874
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
this.logger.log(`VS Code opened successfully with ${extension} extension and remote debugging`);
|
|
878
|
+
return { success: true, message: `VS Code opened with ${extension} extension`, method: 'applescript' };
|
|
879
|
+
} catch (error) {
|
|
880
|
+
this.logger.log(`Error opening VS Code with ${extension}:`, error.message);
|
|
881
|
+
return { success: false, error: error.message, method: 'applescript' };
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Open Replit Agent with optional repository path
|
|
887
|
+
* @param {string} repoPath - Optional repository path to open
|
|
888
|
+
* @returns {Promise<Object>} Result object with success status and details
|
|
889
|
+
*/
|
|
890
|
+
async openReplit(repoPath = null) {
|
|
891
|
+
try {
|
|
892
|
+
this.logger.log('Opening Replit...');
|
|
893
|
+
|
|
894
|
+
// Replit typically uses web browser, so we'll open the Replit website
|
|
895
|
+
const replitUrl = repoPath
|
|
896
|
+
? `https://replit.com/@replit/agent?path=${encodeURIComponent(repoPath)}`
|
|
897
|
+
: 'https://replit.com/@replit/agent';
|
|
898
|
+
|
|
899
|
+
let command;
|
|
900
|
+
if (this.platform === 'darwin') {
|
|
901
|
+
command = `open "${replitUrl}"`;
|
|
902
|
+
} else if (this.platform === 'win32') {
|
|
903
|
+
command = `start "" "${replitUrl}"`;
|
|
904
|
+
} else {
|
|
905
|
+
command = `xdg-open "${replitUrl}"`;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
execSync(command, { stdio: 'pipe' });
|
|
909
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
910
|
+
|
|
911
|
+
this.logger.log('Replit opened successfully');
|
|
912
|
+
return { success: true, message: 'Replit opened in browser', method: 'applescript' };
|
|
913
|
+
} catch (error) {
|
|
914
|
+
this.logger.log('Error opening Replit:', error.message);
|
|
915
|
+
return { success: false, error: error.message, method: 'applescript' };
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
172
919
|
/**
|
|
173
920
|
* Handle Antigravity quota limit by automatically switching models
|
|
174
921
|
* @returns {Promise<{success: boolean, model?: string, error?: string}>}
|
|
@@ -224,7 +971,7 @@ class AppleScriptManager {
|
|
|
224
971
|
|
|
225
972
|
-- Look for model dropdown/menu items
|
|
226
973
|
-- Try alternative models in order of preference
|
|
227
|
-
set modelNames to {"Gemini 3 Pro (Low)", "Claude Sonnet 4.5", "Claude Sonnet 4.5 (Thinking)", "GPT-OSS 120B (Medium)"}
|
|
974
|
+
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)"}
|
|
228
975
|
|
|
229
976
|
repeat with modelName in modelNames
|
|
230
977
|
try
|
|
@@ -233,7 +980,7 @@ class AppleScriptManager {
|
|
|
233
980
|
|
|
234
981
|
-- Try menu items
|
|
235
982
|
try
|
|
236
|
-
set modelItems to menu items of menu 1 of window 1 whose name
|
|
983
|
+
set modelItems to menu items of menu 1 of window 1 whose name contains modelName
|
|
237
984
|
end try
|
|
238
985
|
|
|
239
986
|
-- Try buttons if no menu items found
|
|
@@ -243,6 +990,22 @@ class AppleScriptManager {
|
|
|
243
990
|
end try
|
|
244
991
|
end if
|
|
245
992
|
|
|
993
|
+
-- Search in groups
|
|
994
|
+
if (count of modelItems) = 0 then
|
|
995
|
+
try
|
|
996
|
+
set allGroups to groups of window 1
|
|
997
|
+
repeat with grp in allGroups
|
|
998
|
+
try
|
|
999
|
+
set matchingItems to (every UI element of grp whose name contains modelName)
|
|
1000
|
+
if (count of matchingItems) > 0 then
|
|
1001
|
+
set modelItems to matchingItems
|
|
1002
|
+
exit repeat
|
|
1003
|
+
end if
|
|
1004
|
+
end try
|
|
1005
|
+
end repeat
|
|
1006
|
+
end try
|
|
1007
|
+
end if
|
|
1008
|
+
|
|
246
1009
|
if (count of modelItems) > 0 then
|
|
247
1010
|
click item 1 of modelItems
|
|
248
1011
|
delay 0.8
|
|
@@ -310,12 +1073,27 @@ class AppleScriptManager {
|
|
|
310
1073
|
return await this.openWindsurf(repoPath);
|
|
311
1074
|
case 'antigravity':
|
|
312
1075
|
return await this.openAntigravity(repoPath);
|
|
1076
|
+
case 'vscode':
|
|
1077
|
+
return await this.openVSCode(repoPath);
|
|
1078
|
+
case 'github-copilot':
|
|
1079
|
+
// Open VS Code with GitHub Copilot extension
|
|
1080
|
+
return await this.openVSCodeWithExtension('github-copilot', repoPath);
|
|
1081
|
+
case 'amazon-q':
|
|
1082
|
+
// Open VS Code with Amazon Q extension
|
|
1083
|
+
return await this.openVSCodeWithExtension('amazon-q', repoPath);
|
|
1084
|
+
case 'replit':
|
|
1085
|
+
return await this.openReplit(repoPath);
|
|
313
1086
|
case 'kiro':
|
|
314
1087
|
return await this.openKiro(repoPath);
|
|
1088
|
+
case 'claude':
|
|
1089
|
+
case 'claude-code':
|
|
1090
|
+
return await this.openClaude(repoPath);
|
|
1091
|
+
case 'gemini':
|
|
1092
|
+
return await this.openGemini(repoPath);
|
|
315
1093
|
default:
|
|
316
1094
|
return {
|
|
317
1095
|
success: false,
|
|
318
|
-
error: `Unknown IDE: ${ide}. Supported: cursor, windsurf, antigravity, kiro`
|
|
1096
|
+
error: `Unknown IDE: ${ide}. Supported: cursor, windsurf, antigravity, vscode, github-copilot, amazon-q, replit, kiro, claude, claude-code, gemini`
|
|
319
1097
|
};
|
|
320
1098
|
}
|
|
321
1099
|
}
|
|
@@ -335,7 +1113,7 @@ class AppleScriptManager {
|
|
|
335
1113
|
};
|
|
336
1114
|
}
|
|
337
1115
|
|
|
338
|
-
const ideName = ide === 'windsurf' ? 'Windsurf' : ide === 'cursor' ? 'Cursor' : ide === 'antigravity' ? 'Antigravity' : ide === 'kiro' ? 'AWS Kiro' : 'Unknown IDE';
|
|
1116
|
+
const ideName = ide === 'windsurf' ? 'Windsurf' : ide === 'cursor' ? 'Cursor' : ide === 'antigravity' ? 'Antigravity' : ide === 'kiro' ? 'AWS Kiro' : (ide === 'claude' || ide === 'claude-code') ? 'Claude' : 'Unknown IDE';
|
|
339
1117
|
|
|
340
1118
|
this.logger.log(t('auto.direct.ide.detected', { ide: ideName }));
|
|
341
1119
|
|
|
@@ -416,6 +1194,32 @@ class AppleScriptManager {
|
|
|
416
1194
|
end tell
|
|
417
1195
|
end tell
|
|
418
1196
|
`;
|
|
1197
|
+
} else if (ide === 'vscode' || ide === 'github-copilot' || ide === 'amazon-q') {
|
|
1198
|
+
// AppleScript for VS Code (including GitHub Copilot and Amazon Q extensions)
|
|
1199
|
+
appleScript = `
|
|
1200
|
+
tell application "System Events"
|
|
1201
|
+
tell process "Code"
|
|
1202
|
+
set frontmost to true
|
|
1203
|
+
delay 1.0
|
|
1204
|
+
|
|
1205
|
+
-- Focus GitHub Copilot chat with Cmd+Shift+I or open chat panel
|
|
1206
|
+
key code 34 using {command down, shift down}
|
|
1207
|
+
delay 1.0
|
|
1208
|
+
|
|
1209
|
+
-- Type the message using clipboard (more reliable)
|
|
1210
|
+
set the clipboard to "${text.replace(/"/g, '\\"')}"
|
|
1211
|
+
delay 0.5
|
|
1212
|
+
key code 9 using {command down} -- Cmd+V to paste
|
|
1213
|
+
delay 0.5
|
|
1214
|
+
|
|
1215
|
+
-- Press Cmd+Enter to send
|
|
1216
|
+
key code 36 using {command down}
|
|
1217
|
+
delay 1.0
|
|
1218
|
+
|
|
1219
|
+
return "Message sent to VS Code"
|
|
1220
|
+
end tell
|
|
1221
|
+
end tell
|
|
1222
|
+
`;
|
|
419
1223
|
} else if (ide === 'antigravity') {
|
|
420
1224
|
// AppleScript for Google Antigravity - Cmd+Shift+L opens Agent chat (non-toggle)
|
|
421
1225
|
appleScript = `
|
|
@@ -452,11 +1256,11 @@ class AppleScriptManager {
|
|
|
452
1256
|
on error
|
|
453
1257
|
set processName to "Kiro"
|
|
454
1258
|
end try
|
|
455
|
-
|
|
1259
|
+
|
|
456
1260
|
tell process processName
|
|
457
1261
|
set frontmost to true
|
|
458
1262
|
delay 1.0
|
|
459
|
-
|
|
1263
|
+
|
|
460
1264
|
-- Attempt to focus chat via standard AI IDE shortcuts
|
|
461
1265
|
-- Try Cmd+L (Cursor/Windsurf standard)
|
|
462
1266
|
try
|
|
@@ -469,22 +1273,42 @@ class AppleScriptManager {
|
|
|
469
1273
|
-- Use clipboard for more reliable text entry than keystrokes
|
|
470
1274
|
set the clipboard to "${text.replace(/"/g, '\\"')}"
|
|
471
1275
|
delay 0.3
|
|
472
|
-
|
|
1276
|
+
|
|
473
1277
|
-- Paste
|
|
474
1278
|
key code 9 using {command down}
|
|
475
1279
|
delay 0.5
|
|
476
|
-
|
|
1280
|
+
|
|
477
1281
|
-- Send Enter
|
|
478
1282
|
key code 36
|
|
479
1283
|
delay 0.5
|
|
480
1284
|
end tell
|
|
481
1285
|
end tell
|
|
482
1286
|
`;
|
|
1287
|
+
} else if (ide === 'claude' || ide === 'claude-code') {
|
|
1288
|
+
// Claude CLI text sending - simplified Terminal approach
|
|
1289
|
+
this.logger.log('🚀 [Claude] Sending text to Claude terminal...');
|
|
1290
|
+
|
|
1291
|
+
appleScript = `
|
|
1292
|
+
tell application "System Events"
|
|
1293
|
+
tell process "Terminal"
|
|
1294
|
+
set frontmost to true
|
|
1295
|
+
delay 0.5
|
|
1296
|
+
|
|
1297
|
+
-- Type the text
|
|
1298
|
+
keystroke "${text.replace(/"/g, '\\"')}"
|
|
1299
|
+
delay 1.0
|
|
1300
|
+
|
|
1301
|
+
-- Submit with return
|
|
1302
|
+
keystroke return
|
|
1303
|
+
delay 0.3
|
|
1304
|
+
end tell
|
|
1305
|
+
end tell
|
|
1306
|
+
`;
|
|
483
1307
|
} else {
|
|
484
1308
|
return {
|
|
485
1309
|
success: false,
|
|
486
1310
|
error: `Unsupported IDE for AppleScript: ${ide}`,
|
|
487
|
-
note: 'AppleScript is only supported for Cursor, Windsurf, Antigravity, VS Code,
|
|
1311
|
+
note: 'AppleScript is only supported for Cursor, Windsurf, Antigravity, VS Code, GitHub Copilot, Amazon Q, AWS Kiro, and Claude'
|
|
488
1312
|
};
|
|
489
1313
|
}
|
|
490
1314
|
|
|
@@ -1138,6 +1962,260 @@ class AppleScriptManager {
|
|
|
1138
1962
|
throw error;
|
|
1139
1963
|
}
|
|
1140
1964
|
}
|
|
1965
|
+
|
|
1966
|
+
/**
|
|
1967
|
+
* Check if an IDE is running and accessible
|
|
1968
|
+
* @param {string} ide - The IDE name
|
|
1969
|
+
* @returns {Promise<Object>} Result object with success status and details
|
|
1970
|
+
*/
|
|
1971
|
+
async getIDEStatus(ide) {
|
|
1972
|
+
if (!ide || typeof ide !== 'string') {
|
|
1973
|
+
return {
|
|
1974
|
+
success: false,
|
|
1975
|
+
error: 'Invalid IDE name provided',
|
|
1976
|
+
running: false
|
|
1977
|
+
};
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
try {
|
|
1981
|
+
this.logger.log(`🔍 Checking status of ${ide}...`);
|
|
1982
|
+
|
|
1983
|
+
switch (ide.toLowerCase()) {
|
|
1984
|
+
case 'cursor':
|
|
1985
|
+
return await this._checkCursorStatus();
|
|
1986
|
+
case 'vscode':
|
|
1987
|
+
return await this._checkVSCodeStatus();
|
|
1988
|
+
case 'windsurf':
|
|
1989
|
+
return await this._checkWindsurfStatus();
|
|
1990
|
+
case 'antigravity':
|
|
1991
|
+
return await this._checkAntigravityStatus();
|
|
1992
|
+
case 'cline':
|
|
1993
|
+
return await this._checkClineStatus();
|
|
1994
|
+
default:
|
|
1995
|
+
return {
|
|
1996
|
+
success: false,
|
|
1997
|
+
error: `Unsupported IDE: ${ide}`,
|
|
1998
|
+
running: false
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
} catch (error) {
|
|
2002
|
+
return {
|
|
2003
|
+
success: false,
|
|
2004
|
+
error: `Failed to check IDE status: ${error.message}`,
|
|
2005
|
+
running: false
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
/**
|
|
2011
|
+
* Check Cursor status
|
|
2012
|
+
* @private
|
|
2013
|
+
*/
|
|
2014
|
+
async _checkCursorStatus() {
|
|
2015
|
+
try {
|
|
2016
|
+
const script = `
|
|
2017
|
+
tell application "System Events"
|
|
2018
|
+
set isRunning to (exists process "Cursor")
|
|
2019
|
+
if isRunning then
|
|
2020
|
+
try
|
|
2021
|
+
tell application "Cursor" to activate
|
|
2022
|
+
delay 0.5
|
|
2023
|
+
return "running"
|
|
2024
|
+
on error
|
|
2025
|
+
return "error"
|
|
2026
|
+
end try
|
|
2027
|
+
else
|
|
2028
|
+
return "not_running"
|
|
2029
|
+
end if
|
|
2030
|
+
end tell
|
|
2031
|
+
`;
|
|
2032
|
+
|
|
2033
|
+
const result = execSync(`osascript -e '${script}'`, { stdio: 'pipe', encoding: 'utf8' });
|
|
2034
|
+
const running = result.includes('running');
|
|
2035
|
+
|
|
2036
|
+
return {
|
|
2037
|
+
success: true,
|
|
2038
|
+
running,
|
|
2039
|
+
status: running ? 'running' : 'not_running',
|
|
2040
|
+
message: running ? 'Cursor is running and accessible' : 'Cursor is not running'
|
|
2041
|
+
};
|
|
2042
|
+
} catch (error) {
|
|
2043
|
+
return {
|
|
2044
|
+
success: false,
|
|
2045
|
+
error: `Failed to check Cursor status: ${error.message}`,
|
|
2046
|
+
running: false
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
/**
|
|
2052
|
+
* Check VS Code status
|
|
2053
|
+
* @private
|
|
2054
|
+
*/
|
|
2055
|
+
async _checkVSCodeStatus() {
|
|
2056
|
+
try {
|
|
2057
|
+
const script = `
|
|
2058
|
+
tell application "System Events"
|
|
2059
|
+
set isRunning to (exists process "Code")
|
|
2060
|
+
if isRunning then
|
|
2061
|
+
try
|
|
2062
|
+
tell application "Visual Studio Code" to activate
|
|
2063
|
+
delay 0.5
|
|
2064
|
+
return "running"
|
|
2065
|
+
on error
|
|
2066
|
+
return "error"
|
|
2067
|
+
end try
|
|
2068
|
+
else
|
|
2069
|
+
return "not_running"
|
|
2070
|
+
end if
|
|
2071
|
+
end tell
|
|
2072
|
+
`;
|
|
2073
|
+
|
|
2074
|
+
const result = execSync(`osascript -e '${script}'`, { stdio: 'pipe', encoding: 'utf8' });
|
|
2075
|
+
const running = result.includes('running');
|
|
2076
|
+
|
|
2077
|
+
return {
|
|
2078
|
+
success: true,
|
|
2079
|
+
running,
|
|
2080
|
+
status: running ? 'running' : 'not_running',
|
|
2081
|
+
message: running ? 'VS Code is running and accessible' : 'VS Code is not running'
|
|
2082
|
+
};
|
|
2083
|
+
} catch (error) {
|
|
2084
|
+
return {
|
|
2085
|
+
success: false,
|
|
2086
|
+
error: `Failed to check VS Code status: ${error.message}`,
|
|
2087
|
+
running: false
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
/**
|
|
2093
|
+
* Check Windsurf status
|
|
2094
|
+
* @private
|
|
2095
|
+
*/
|
|
2096
|
+
async _checkWindsurfStatus() {
|
|
2097
|
+
try {
|
|
2098
|
+
const script = `
|
|
2099
|
+
tell application "System Events"
|
|
2100
|
+
set isRunning to (exists process "Windsurf")
|
|
2101
|
+
if isRunning then
|
|
2102
|
+
try
|
|
2103
|
+
tell application "Windsurf" to activate
|
|
2104
|
+
delay 0.5
|
|
2105
|
+
return "running"
|
|
2106
|
+
on error
|
|
2107
|
+
return "error"
|
|
2108
|
+
end try
|
|
2109
|
+
else
|
|
2110
|
+
return "not_running"
|
|
2111
|
+
end if
|
|
2112
|
+
end tell
|
|
2113
|
+
`;
|
|
2114
|
+
|
|
2115
|
+
const result = execSync(`osascript -e '${script}'`, { stdio: 'pipe', encoding: 'utf8' });
|
|
2116
|
+
const running = result.includes('running');
|
|
2117
|
+
|
|
2118
|
+
return {
|
|
2119
|
+
success: true,
|
|
2120
|
+
running,
|
|
2121
|
+
status: running ? 'running' : 'not_running',
|
|
2122
|
+
message: running ? 'Windsurf is running and accessible' : 'Windsurf is not running'
|
|
2123
|
+
};
|
|
2124
|
+
} catch (error) {
|
|
2125
|
+
return {
|
|
2126
|
+
success: false,
|
|
2127
|
+
error: `Failed to check Windsurf status: ${error.message}`,
|
|
2128
|
+
running: false
|
|
2129
|
+
};
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
/**
|
|
2134
|
+
* Check Antigravity status
|
|
2135
|
+
* @private
|
|
2136
|
+
*/
|
|
2137
|
+
async _checkAntigravityStatus() {
|
|
2138
|
+
try {
|
|
2139
|
+
// Antigravity runs in browser, check if Chrome/Edge is running with Antigravity tab
|
|
2140
|
+
const script = `
|
|
2141
|
+
tell application "System Events"
|
|
2142
|
+
set isChromeRunning to (exists process "Google Chrome")
|
|
2143
|
+
set isEdgeRunning to (exists process "Microsoft Edge")
|
|
2144
|
+
|
|
2145
|
+
if isChromeRunning then
|
|
2146
|
+
try
|
|
2147
|
+
tell application "Google Chrome" to activate
|
|
2148
|
+
delay 0.5
|
|
2149
|
+
return "running"
|
|
2150
|
+
on error
|
|
2151
|
+
return "error"
|
|
2152
|
+
end try
|
|
2153
|
+
else if isEdgeRunning then
|
|
2154
|
+
try
|
|
2155
|
+
tell application "Microsoft Edge" to activate
|
|
2156
|
+
delay 0.5
|
|
2157
|
+
return "running"
|
|
2158
|
+
on error
|
|
2159
|
+
return "error"
|
|
2160
|
+
end try
|
|
2161
|
+
else
|
|
2162
|
+
return "not_running"
|
|
2163
|
+
end if
|
|
2164
|
+
end tell
|
|
2165
|
+
`;
|
|
2166
|
+
|
|
2167
|
+
const result = execSync(`osascript -e '${script}'`, { stdio: 'pipe', encoding: 'utf8' });
|
|
2168
|
+
const running = result.includes('running');
|
|
2169
|
+
|
|
2170
|
+
return {
|
|
2171
|
+
success: true,
|
|
2172
|
+
running,
|
|
2173
|
+
status: running ? 'running' : 'not_running',
|
|
2174
|
+
message: running ? 'Browser is running (Antigravity accessible)' : 'Browser is not running'
|
|
2175
|
+
};
|
|
2176
|
+
} catch (error) {
|
|
2177
|
+
return {
|
|
2178
|
+
success: false,
|
|
2179
|
+
error: `Failed to check Antigravity status: ${error.message}`,
|
|
2180
|
+
running: false
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
/**
|
|
2186
|
+
* Check Cline status
|
|
2187
|
+
* @private
|
|
2188
|
+
*/
|
|
2189
|
+
async _checkClineStatus() {
|
|
2190
|
+
try {
|
|
2191
|
+
// Cline runs in VS Code as an extension
|
|
2192
|
+
const vscodeStatus = await this._checkVSCodeStatus();
|
|
2193
|
+
|
|
2194
|
+
if (!vscodeStatus.running) {
|
|
2195
|
+
return {
|
|
2196
|
+
success: true,
|
|
2197
|
+
running: false,
|
|
2198
|
+
status: 'not_running',
|
|
2199
|
+
message: 'Cline requires VS Code to be running'
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
// For now, assume Cline is available if VS Code is running
|
|
2204
|
+
// In a more sophisticated implementation, we could check if the extension is installed
|
|
2205
|
+
return {
|
|
2206
|
+
success: true,
|
|
2207
|
+
running: true,
|
|
2208
|
+
status: 'running',
|
|
2209
|
+
message: 'Cline is available (VS Code running)'
|
|
2210
|
+
};
|
|
2211
|
+
} catch (error) {
|
|
2212
|
+
return {
|
|
2213
|
+
success: false,
|
|
2214
|
+
error: `Failed to check Cline status: ${error.message}`,
|
|
2215
|
+
running: false
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
1141
2219
|
}
|
|
1142
2220
|
|
|
1143
2221
|
module.exports = { AppleScriptManager };
|