vibecodingmachine-core 2025.12.6-1702 → 2025.12.24-2348
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/package.json +1 -1
- package/scripts/setup-database.js +108 -0
- package/src/compliance/compliance-manager.js +249 -0
- package/src/compliance/compliance-prompt.js +183 -0
- package/src/database/migrations.js +289 -0
- package/src/database/user-database-client.js +266 -0
- package/src/database/user-schema.js +118 -0
- package/src/ide-integration/applescript-manager.cjs +76 -129
- package/src/ide-integration/applescript-manager.js +62 -12
- package/src/ide-integration/claude-code-cli-manager.cjs +120 -1
- package/src/ide-integration/provider-manager.cjs +67 -1
- package/src/index.cjs +3 -1
- package/src/index.js +7 -0
- package/src/llm/direct-llm-manager.cjs +110 -73
- package/src/localization/index.js +148 -0
- package/src/localization/translations/en.js +675 -0
- package/src/localization/translations/es.js +676 -0
- package/src/quota-management/index.js +108 -0
- package/src/sync/sync-engine.js +32 -10
- package/src/utils/download-with-progress.js +92 -0
- package/src/utils/electron-update-checker.js +7 -0
- package/src/utils/env-helpers.js +54 -0
- package/src/utils/requirement-helpers.js +745 -49
- package/src/utils/requirements-parser.js +21 -7
- package/src/utils/update-checker.js +7 -0
- package/test-quota-system.js +67 -0
- package/test-requirement-stats.js +66 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Handles AppleScript-based interactions with IDEs that don't support CDP
|
|
3
3
|
|
|
4
4
|
const { execSync } = require('child_process');
|
|
5
|
+
const { t } = require('../localization');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* AppleScript Manager for IDE interactions
|
|
@@ -182,11 +183,43 @@ class AppleScriptManager {
|
|
|
182
183
|
set frontmost to true
|
|
183
184
|
delay 0.8
|
|
184
185
|
|
|
185
|
-
-- Try to find
|
|
186
|
+
-- Try to find model selection button with multiple possible texts
|
|
186
187
|
try
|
|
187
|
-
set
|
|
188
|
-
|
|
189
|
-
|
|
188
|
+
set buttonTexts to {"Select another model", "Switch model", "Change model", "Select model", "Try another model", "Choose model"}
|
|
189
|
+
set foundButton to missing value
|
|
190
|
+
|
|
191
|
+
-- Search for button in window 1
|
|
192
|
+
repeat with buttonText in buttonTexts
|
|
193
|
+
try
|
|
194
|
+
set matchingButtons to buttons of window 1 whose name contains buttonText
|
|
195
|
+
if (count of matchingButtons) > 0 then
|
|
196
|
+
set foundButton to item 1 of matchingButtons
|
|
197
|
+
exit repeat
|
|
198
|
+
end if
|
|
199
|
+
end try
|
|
200
|
+
end repeat
|
|
201
|
+
|
|
202
|
+
-- Also search in groups within window 1
|
|
203
|
+
if foundButton is missing value then
|
|
204
|
+
repeat with buttonText in buttonTexts
|
|
205
|
+
try
|
|
206
|
+
set allGroups to groups of window 1
|
|
207
|
+
repeat with grp in allGroups
|
|
208
|
+
try
|
|
209
|
+
set matchingButtons to buttons of grp whose name contains buttonText
|
|
210
|
+
if (count of matchingButtons) > 0 then
|
|
211
|
+
set foundButton to item 1 of matchingButtons
|
|
212
|
+
exit repeat
|
|
213
|
+
end if
|
|
214
|
+
end try
|
|
215
|
+
end repeat
|
|
216
|
+
if foundButton is not missing value then exit repeat
|
|
217
|
+
end try
|
|
218
|
+
end repeat
|
|
219
|
+
end if
|
|
220
|
+
|
|
221
|
+
if foundButton is not missing value then
|
|
222
|
+
click foundButton
|
|
190
223
|
delay 1.5
|
|
191
224
|
|
|
192
225
|
-- Look for model dropdown/menu items
|
|
@@ -195,20 +228,38 @@ class AppleScriptManager {
|
|
|
195
228
|
|
|
196
229
|
repeat with modelName in modelNames
|
|
197
230
|
try
|
|
198
|
-
-- Try to find and click this model option
|
|
199
|
-
set modelItems to
|
|
231
|
+
-- Try to find and click this model option in various locations
|
|
232
|
+
set modelItems to {}
|
|
233
|
+
|
|
234
|
+
-- Try menu items
|
|
235
|
+
try
|
|
236
|
+
set modelItems to menu items of menu 1 of window 1 whose name is modelName
|
|
237
|
+
end try
|
|
238
|
+
|
|
239
|
+
-- Try buttons if no menu items found
|
|
240
|
+
if (count of modelItems) = 0 then
|
|
241
|
+
try
|
|
242
|
+
set modelItems to buttons of window 1 whose name contains modelName
|
|
243
|
+
end try
|
|
244
|
+
end if
|
|
245
|
+
|
|
200
246
|
if (count of modelItems) > 0 then
|
|
201
247
|
click item 1 of modelItems
|
|
202
248
|
delay 0.8
|
|
203
249
|
|
|
204
250
|
-- Click "Accept all" or similar confirmation button
|
|
205
251
|
try
|
|
206
|
-
set
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
252
|
+
set acceptTexts to {"Accept", "OK", "Confirm", "Continue"}
|
|
253
|
+
repeat with acceptText in acceptTexts
|
|
254
|
+
try
|
|
255
|
+
set acceptButtons to buttons of window 1 whose name contains acceptText
|
|
256
|
+
if (count of acceptButtons) > 0 then
|
|
257
|
+
click item 1 of acceptButtons
|
|
258
|
+
delay 0.5
|
|
259
|
+
return "success:" & modelName
|
|
260
|
+
end if
|
|
261
|
+
end try
|
|
262
|
+
end repeat
|
|
212
263
|
end try
|
|
213
264
|
|
|
214
265
|
return "success:" & modelName
|
|
@@ -286,7 +337,7 @@ class AppleScriptManager {
|
|
|
286
337
|
|
|
287
338
|
const ideName = ide === 'windsurf' ? 'Windsurf' : ide === 'cursor' ? 'Cursor' : ide === 'antigravity' ? 'Antigravity' : ide === 'kiro' ? 'AWS Kiro' : 'Unknown IDE';
|
|
288
339
|
|
|
289
|
-
this.logger.log(
|
|
340
|
+
this.logger.log(t('auto.direct.ide.detected', { ide: ideName }));
|
|
290
341
|
|
|
291
342
|
try {
|
|
292
343
|
let appleScript;
|
|
@@ -340,118 +391,14 @@ class AppleScriptManager {
|
|
|
340
391
|
end tell
|
|
341
392
|
`;
|
|
342
393
|
} else if (ide === 'cursor') {
|
|
343
|
-
// AppleScript for Cursor -
|
|
394
|
+
// AppleScript for Cursor - Using working shortcuts
|
|
344
395
|
appleScript = `
|
|
345
396
|
tell application "System Events"
|
|
346
397
|
tell process "Cursor"
|
|
347
398
|
set frontmost to true
|
|
348
|
-
delay 1
|
|
349
|
-
|
|
350
|
-
-- DYNAMIC SCREENSHOT-BASED CHAT INPUT TARGETING
|
|
351
|
-
-- Method 1: Take screenshot of the monitor where Cursor is running
|
|
352
|
-
try
|
|
353
|
-
-- Get the bounds of the Cursor window to determine which monitor it's on
|
|
354
|
-
set cursorBounds to bounds of window 1
|
|
355
|
-
set cursorX to item 1 of cursorBounds
|
|
356
|
-
set cursorY to item 2 of cursorBounds
|
|
357
|
-
|
|
358
|
-
-- Take a screenshot of the specific monitor where Cursor is located
|
|
359
|
-
set screenshotPath to (path to desktop as string) & "cursor_screenshot.png"
|
|
360
|
-
|
|
361
|
-
-- Use screencapture with display selection based on Cursor window position
|
|
362
|
-
-- If Cursor is on the right side (aux monitor), use display 2
|
|
363
|
-
if cursorX > 1000 then
|
|
364
|
-
do shell script "screencapture -D 2 -x " & quoted form of POSIX path of screenshotPath
|
|
365
|
-
else
|
|
366
|
-
do shell script "screencapture -D 1 -x " & quoted form of POSIX path of screenshotPath
|
|
367
|
-
end if
|
|
368
|
-
delay 0.5
|
|
369
|
-
|
|
370
|
-
-- Use Python with PIL to analyze the screenshot and find text input area
|
|
371
|
-
set pythonScript to "
|
|
372
|
-
import sys
|
|
373
|
-
import os
|
|
374
|
-
from PIL import Image, ImageDraw
|
|
375
|
-
import subprocess
|
|
376
|
-
|
|
377
|
-
try:
|
|
378
|
-
# Read the screenshot
|
|
379
|
-
img = Image.open('" & POSIX path of screenshotPath & "')
|
|
380
|
-
width, height = img.size
|
|
381
|
-
|
|
382
|
-
# Convert to grayscale for analysis
|
|
383
|
-
gray = img.convert('L')
|
|
384
|
-
|
|
385
|
-
# Get screen dimensions to calculate relative positions
|
|
386
|
-
screen_width = width
|
|
387
|
-
screen_height = height
|
|
388
|
-
|
|
389
|
-
# Determine if this is the auxiliary monitor based on Cursor window position
|
|
390
|
-
cursor_x = " & cursorX & "
|
|
391
|
-
is_aux_monitor = cursor_x > 1000
|
|
392
|
-
|
|
393
|
-
if is_aux_monitor:
|
|
394
|
-
# For auxiliary monitor, adjust coordinates
|
|
395
|
-
# Chat input is typically in the right side of the aux monitor
|
|
396
|
-
candidates = [
|
|
397
|
-
(int(screen_width * 0.8), int(screen_height * 0.85)), # Bottom-right
|
|
398
|
-
(int(screen_width * 0.75), int(screen_height * 0.8)), # Slightly up
|
|
399
|
-
(int(screen_width * 0.85), int(screen_height * 0.8)), # More right
|
|
400
|
-
(int(screen_width * 0.7), int(screen_height * 0.9)), # Bottom-left of right area
|
|
401
|
-
]
|
|
402
|
-
else:
|
|
403
|
-
# For main monitor, use standard coordinates
|
|
404
|
-
candidates = [
|
|
405
|
-
(int(screen_width * 0.8), int(screen_height * 0.85)), # Bottom-right
|
|
406
|
-
(int(screen_width * 0.75), int(screen_height * 0.8)), # Slightly up
|
|
407
|
-
(int(screen_width * 0.85), int(screen_height * 0.8)), # More right
|
|
408
|
-
(int(screen_width * 0.7), int(screen_height * 0.9)), # Bottom-left of right area
|
|
409
|
-
]
|
|
410
|
-
|
|
411
|
-
# Use the first candidate (bottom-right area)
|
|
412
|
-
x, y = candidates[0]
|
|
413
|
-
print(f'{x},{y}')
|
|
414
|
-
|
|
415
|
-
except Exception as e:
|
|
416
|
-
# Fallback to estimated coordinates based on monitor
|
|
417
|
-
if " & cursorX & " > 1000:
|
|
418
|
-
print('1000,600') # Aux monitor fallback
|
|
419
|
-
else:
|
|
420
|
-
print('800,500') # Main monitor fallback
|
|
421
|
-
"
|
|
422
|
-
|
|
423
|
-
-- Execute Python script to find coordinates
|
|
424
|
-
set coordinates to do shell script "python3 -c " & quoted form of pythonScript
|
|
425
|
-
delay 0.3
|
|
426
|
-
|
|
427
|
-
-- Click at the detected coordinates
|
|
428
|
-
do shell script "/usr/local/bin/cliclick c:" & coordinates
|
|
429
|
-
delay 0.5
|
|
430
|
-
|
|
431
|
-
-- Clean up screenshot
|
|
432
|
-
do shell script "rm " & quoted form of POSIX path of screenshotPath
|
|
433
|
-
|
|
434
|
-
on error
|
|
435
|
-
-- Fallback: Use estimated coordinates if screenshot analysis fails
|
|
436
|
-
try
|
|
437
|
-
-- Click in the main editor area to escape terminal
|
|
438
|
-
do shell script "/usr/local/bin/cliclick c:600,400"
|
|
439
|
-
delay 0.3
|
|
440
|
-
|
|
441
|
-
-- Click in the chat panel area to focus it
|
|
442
|
-
do shell script "/usr/local/bin/cliclick c:1200,500"
|
|
443
|
-
delay 0.3
|
|
444
|
-
|
|
445
|
-
-- Click in the chat input area specifically
|
|
446
|
-
do shell script "/usr/local/bin/cliclick c:1100,700"
|
|
447
|
-
delay 0.3
|
|
448
|
-
|
|
449
|
-
on error
|
|
450
|
-
delay 0.3
|
|
451
|
-
end try
|
|
452
|
-
end try
|
|
399
|
+
delay 1.0
|
|
453
400
|
|
|
454
|
-
-- Open new chat session with Cmd+T to
|
|
401
|
+
-- Open new chat session with Cmd+T (proven to work)
|
|
455
402
|
key code 17 using {command down} -- Cmd+T
|
|
456
403
|
delay 1.0
|
|
457
404
|
|
|
@@ -462,8 +409,8 @@ except Exception as e:
|
|
|
462
409
|
delay 0.5
|
|
463
410
|
|
|
464
411
|
-- Press Cmd+Enter to send (more reliable than just Enter)
|
|
465
|
-
key code 36 using {command down}
|
|
466
|
-
delay 1
|
|
412
|
+
key code 36 using {command down} -- Cmd+Enter
|
|
413
|
+
delay 1.0
|
|
467
414
|
|
|
468
415
|
return "Message sent via AppleScript"
|
|
469
416
|
end tell
|
|
@@ -543,7 +490,7 @@ except Exception as e:
|
|
|
543
490
|
|
|
544
491
|
execSync(`osascript -e '${appleScript}'`, { stdio: 'pipe' });
|
|
545
492
|
|
|
546
|
-
this.logger.log(
|
|
493
|
+
this.logger.log(t('auto.direct.ide.sent.applescript', { ide: ideName }));
|
|
547
494
|
return {
|
|
548
495
|
success: true,
|
|
549
496
|
method: 'applescript',
|
|
@@ -552,14 +499,14 @@ except Exception as e:
|
|
|
552
499
|
};
|
|
553
500
|
|
|
554
501
|
} catch (error) {
|
|
555
|
-
this.logger.log(
|
|
502
|
+
this.logger.log(`❌ AppleScript interaction failed for ${ideName}:`, error.message);
|
|
556
503
|
|
|
557
|
-
//
|
|
504
|
+
// Return failure - DO NOT fallback to simulation
|
|
558
505
|
return {
|
|
559
|
-
success:
|
|
560
|
-
|
|
561
|
-
message: `
|
|
562
|
-
note:
|
|
506
|
+
success: false,
|
|
507
|
+
error: error.message,
|
|
508
|
+
message: `Failed to send message to ${ideName}`,
|
|
509
|
+
note: `AppleScript automation failed. Error: ${error.message}`
|
|
563
510
|
};
|
|
564
511
|
}
|
|
565
512
|
}
|
|
@@ -576,11 +576,43 @@ class AppleScriptManager {
|
|
|
576
576
|
set frontmost to true
|
|
577
577
|
delay 0.8
|
|
578
578
|
|
|
579
|
-
-- Try to find
|
|
579
|
+
-- Try to find model selection button with multiple possible texts
|
|
580
580
|
try
|
|
581
|
-
set
|
|
582
|
-
|
|
583
|
-
|
|
581
|
+
set buttonTexts to {"Select another model", "Switch model", "Change model", "Select model", "Try another model", "Choose model"}
|
|
582
|
+
set foundButton to missing value
|
|
583
|
+
|
|
584
|
+
-- Search for button in window 1
|
|
585
|
+
repeat with buttonText in buttonTexts
|
|
586
|
+
try
|
|
587
|
+
set matchingButtons to buttons of window 1 whose name contains buttonText
|
|
588
|
+
if (count of matchingButtons) > 0 then
|
|
589
|
+
set foundButton to item 1 of matchingButtons
|
|
590
|
+
exit repeat
|
|
591
|
+
end if
|
|
592
|
+
end try
|
|
593
|
+
end repeat
|
|
594
|
+
|
|
595
|
+
-- Also search in groups within window 1
|
|
596
|
+
if foundButton is missing value then
|
|
597
|
+
repeat with buttonText in buttonTexts
|
|
598
|
+
try
|
|
599
|
+
set allGroups to groups of window 1
|
|
600
|
+
repeat with grp in allGroups
|
|
601
|
+
try
|
|
602
|
+
set matchingButtons to buttons of grp whose name contains buttonText
|
|
603
|
+
if (count of matchingButtons) > 0 then
|
|
604
|
+
set foundButton to item 1 of matchingButtons
|
|
605
|
+
exit repeat
|
|
606
|
+
end if
|
|
607
|
+
end try
|
|
608
|
+
end repeat
|
|
609
|
+
if foundButton is not missing value then exit repeat
|
|
610
|
+
end try
|
|
611
|
+
end repeat
|
|
612
|
+
end if
|
|
613
|
+
|
|
614
|
+
if foundButton is not missing value then
|
|
615
|
+
click foundButton
|
|
584
616
|
delay 1.5
|
|
585
617
|
|
|
586
618
|
-- Look for model dropdown/menu items
|
|
@@ -589,20 +621,38 @@ class AppleScriptManager {
|
|
|
589
621
|
|
|
590
622
|
repeat with modelName in modelNames
|
|
591
623
|
try
|
|
592
|
-
-- Try to find and click this model option
|
|
593
|
-
set modelItems to
|
|
624
|
+
-- Try to find and click this model option in various locations
|
|
625
|
+
set modelItems to {}
|
|
626
|
+
|
|
627
|
+
-- Try menu items
|
|
628
|
+
try
|
|
629
|
+
set modelItems to menu items of menu 1 of window 1 whose name is modelName
|
|
630
|
+
end try
|
|
631
|
+
|
|
632
|
+
-- Try buttons if no menu items found
|
|
633
|
+
if (count of modelItems) = 0 then
|
|
634
|
+
try
|
|
635
|
+
set modelItems to buttons of window 1 whose name contains modelName
|
|
636
|
+
end try
|
|
637
|
+
end if
|
|
638
|
+
|
|
594
639
|
if (count of modelItems) > 0 then
|
|
595
640
|
click item 1 of modelItems
|
|
596
641
|
delay 0.8
|
|
597
642
|
|
|
598
643
|
-- Click "Accept all" or similar confirmation button
|
|
599
644
|
try
|
|
600
|
-
set
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
645
|
+
set acceptTexts to {"Accept", "OK", "Confirm", "Continue"}
|
|
646
|
+
repeat with acceptText in acceptTexts
|
|
647
|
+
try
|
|
648
|
+
set acceptButtons to buttons of window 1 whose name contains acceptText
|
|
649
|
+
if (count of acceptButtons) > 0 then
|
|
650
|
+
click item 1 of acceptButtons
|
|
651
|
+
delay 0.5
|
|
652
|
+
return "success:" & modelName
|
|
653
|
+
end if
|
|
654
|
+
end try
|
|
655
|
+
end repeat
|
|
606
656
|
end try
|
|
607
657
|
|
|
608
658
|
return "success:" & modelName
|
|
@@ -3,6 +3,25 @@ const { execSync, spawn } = require('child_process');
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const os = require('os');
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
|
|
8
|
+
// Rate limit patterns for Claude Code
|
|
9
|
+
const RATE_LIMIT_PATTERNS = [
|
|
10
|
+
/quota limit/i,
|
|
11
|
+
/rate limit/i,
|
|
12
|
+
/too many requests/i,
|
|
13
|
+
/limit exceeded/i,
|
|
14
|
+
/quota: \d+\/\d+/i,
|
|
15
|
+
/you'?ve hit your limit/i,
|
|
16
|
+
/resets? (?:at )?\d+[a-z]{2} \(/i,
|
|
17
|
+
/rate limit reached/i,
|
|
18
|
+
/quota exceeded/i,
|
|
19
|
+
/usage limit reached/i,
|
|
20
|
+
/try again in \d+[mh]\d*[ms]?/i,
|
|
21
|
+
/please try again in/i,
|
|
22
|
+
/session limit reached/i,
|
|
23
|
+
/account limit reached/i
|
|
24
|
+
];
|
|
6
25
|
|
|
7
26
|
// Helper function for timestamps
|
|
8
27
|
function getTimestamp() {
|
|
@@ -18,6 +37,7 @@ class ClaudeCodeCLIManager {
|
|
|
18
37
|
constructor() {
|
|
19
38
|
this.logger = console;
|
|
20
39
|
this.runningProcesses = [];
|
|
40
|
+
this.rateLimitInfo = null;
|
|
21
41
|
}
|
|
22
42
|
|
|
23
43
|
/**
|
|
@@ -124,6 +144,14 @@ class ClaudeCodeCLIManager {
|
|
|
124
144
|
this.runningProcesses = [];
|
|
125
145
|
}
|
|
126
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Get rate limit information if the last operation was rate limited
|
|
149
|
+
* @returns {{isRateLimited: boolean, resetTime?: Date, message?: string} | null}
|
|
150
|
+
*/
|
|
151
|
+
getRateLimitInfo() {
|
|
152
|
+
return this.rateLimitInfo || null;
|
|
153
|
+
}
|
|
154
|
+
|
|
127
155
|
/**
|
|
128
156
|
* Send a prompt to Claude Code and wait for completion
|
|
129
157
|
* @param {string} text - The message/prompt to send
|
|
@@ -134,6 +162,61 @@ class ClaudeCodeCLIManager {
|
|
|
134
162
|
* @param {number} options.timeoutMs - Timeout in milliseconds (default 300000 = 5 minutes)
|
|
135
163
|
* @returns {Promise<{success: boolean, output: string, error?: string}>}
|
|
136
164
|
*/
|
|
165
|
+
/**
|
|
166
|
+
* Check if the error indicates a rate limit
|
|
167
|
+
* @param {string} error - The error message
|
|
168
|
+
* @returns {{isRateLimited: boolean, resetTime?: Date, message?: string}}
|
|
169
|
+
*/
|
|
170
|
+
checkRateLimit(error) {
|
|
171
|
+
if (!error) return { isRateLimited: false };
|
|
172
|
+
|
|
173
|
+
const lowerError = error.toLowerCase();
|
|
174
|
+
|
|
175
|
+
// Check for rate limit patterns
|
|
176
|
+
const isRateLimited = RATE_LIMIT_PATTERNS.some(pattern =>
|
|
177
|
+
pattern.test(error)
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
if (!isRateLimited) {
|
|
181
|
+
return { isRateLimited: false };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Try to extract reset time if available
|
|
185
|
+
let resetTime;
|
|
186
|
+
// Match patterns like "resets 6am (America/Buenos_Aires)" or "resets at 6am (America/Buenos_Aires)"
|
|
187
|
+
const resetMatch = error.match(/resets? (?:at )?(\d+)(?::(\d+))?\s*([ap]m)?\s*(\([^)]+\))?/i);
|
|
188
|
+
if (resetMatch) {
|
|
189
|
+
try {
|
|
190
|
+
const now = new Date();
|
|
191
|
+
let hours = parseInt(resetMatch[1]);
|
|
192
|
+
const minutes = resetMatch[2] ? parseInt(resetMatch[2]) : 0;
|
|
193
|
+
const period = resetMatch[3] ? resetMatch[3].toLowerCase() : null;
|
|
194
|
+
|
|
195
|
+
// Convert to 24-hour format if period is specified
|
|
196
|
+
if (period === 'pm' && hours < 12) hours += 12;
|
|
197
|
+
if (period === 'am' && hours === 12) hours = 0;
|
|
198
|
+
|
|
199
|
+
// Create reset time
|
|
200
|
+
resetTime = new Date(now);
|
|
201
|
+
resetTime.setHours(hours, minutes, 0, 0);
|
|
202
|
+
|
|
203
|
+
// If reset time is in the past, assume it's for tomorrow
|
|
204
|
+
if (resetTime <= now) {
|
|
205
|
+
resetTime.setDate(resetTime.getDate() + 1);
|
|
206
|
+
}
|
|
207
|
+
} catch (e) {
|
|
208
|
+
console.error('Error parsing reset time:', e);
|
|
209
|
+
resetTime = null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
isRateLimited: true,
|
|
215
|
+
resetTime,
|
|
216
|
+
message: `Rate limit reached${resetTime ? `, resets at ${resetTime.toLocaleString()}` : ''}`
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
137
220
|
async sendText(text, cwd = process.cwd(), options = {}) {
|
|
138
221
|
if (!this.isInstalled()) {
|
|
139
222
|
return {
|
|
@@ -220,6 +303,24 @@ class ClaudeCodeCLIManager {
|
|
|
220
303
|
const chunkText = chunk.toString();
|
|
221
304
|
stderr += chunkText;
|
|
222
305
|
|
|
306
|
+
// Check for rate limits in stderr
|
|
307
|
+
const rateLimitInfo = this.checkRateLimit(chunkText);
|
|
308
|
+
if (rateLimitInfo.isRateLimited) {
|
|
309
|
+
this.rateLimitInfo = rateLimitInfo;
|
|
310
|
+
console.error(chalk.yellow(`\n⚠️ ${rateLimitInfo.message}`));
|
|
311
|
+
|
|
312
|
+
// If we have a reset time, update the provider manager
|
|
313
|
+
if (rateLimitInfo.resetTime) {
|
|
314
|
+
try {
|
|
315
|
+
const ProviderManager = require('vibecodingmachine-core/src/ide-integration/provider-manager.cjs');
|
|
316
|
+
const providerManager = new ProviderManager();
|
|
317
|
+
providerManager.markRateLimited('claude-code', 'claude-code-cli', rateLimitInfo.message);
|
|
318
|
+
} catch (e) {
|
|
319
|
+
console.error('Failed to update rate limit status:', e);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
223
324
|
// Only show actual errors
|
|
224
325
|
if (chunkText.includes('Error:') || chunkText.includes('Failed:') || chunkText.includes('error')) {
|
|
225
326
|
if (onError) {
|
|
@@ -290,9 +391,27 @@ class ClaudeCodeCLIManager {
|
|
|
290
391
|
});
|
|
291
392
|
});
|
|
292
393
|
} catch (error) {
|
|
394
|
+
// Check if this is a rate limit error
|
|
395
|
+
const rateLimitInfo = this.checkRateLimit(error.message);
|
|
396
|
+
if (rateLimitInfo.isRateLimited) {
|
|
397
|
+
this.rateLimitInfo = rateLimitInfo;
|
|
398
|
+
console.error(chalk.yellow(`\n⚠️ ${rateLimitInfo.message}`));
|
|
399
|
+
|
|
400
|
+
// Update provider manager with rate limit info
|
|
401
|
+
try {
|
|
402
|
+
const ProviderManager = require('vibecodingmachine-core/src/ide-integration/provider-manager.cjs');
|
|
403
|
+
const providerManager = new ProviderManager();
|
|
404
|
+
providerManager.markRateLimited('claude-code', 'claude-code-cli', rateLimitInfo.message);
|
|
405
|
+
} catch (e) {
|
|
406
|
+
console.error('Failed to update rate limit status:', e);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
293
410
|
return {
|
|
294
411
|
success: false,
|
|
295
|
-
error: error.message
|
|
412
|
+
error: error.message,
|
|
413
|
+
rateLimited: rateLimitInfo.isRateLimited,
|
|
414
|
+
rateLimitInfo: rateLimitInfo.isRateLimited ? rateLimitInfo : undefined
|
|
296
415
|
};
|
|
297
416
|
}
|
|
298
417
|
}
|
|
@@ -175,10 +175,24 @@ class ProviderManager {
|
|
|
175
175
|
/**
|
|
176
176
|
* Check if a provider is currently rate limited
|
|
177
177
|
* @param {string} provider - Provider name
|
|
178
|
-
* @param {string} model - Model name
|
|
178
|
+
* @param {string} model - Model name (optional, will check all models if not provided)
|
|
179
179
|
* @returns {boolean}
|
|
180
180
|
*/
|
|
181
181
|
isRateLimited(provider, model) {
|
|
182
|
+
if (!model) {
|
|
183
|
+
// Check if ANY model for this provider is rate limited
|
|
184
|
+
const providerPrefix = `${provider}:`;
|
|
185
|
+
for (const key in this.rateLimits) {
|
|
186
|
+
if (key.startsWith(providerPrefix)) {
|
|
187
|
+
const limit = this.rateLimits[key];
|
|
188
|
+
if (limit && Date.now() < limit.resetTime) {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
182
196
|
const key = `${provider}:${model}`;
|
|
183
197
|
const limit = this.rateLimits[key];
|
|
184
198
|
|
|
@@ -195,6 +209,58 @@ class ProviderManager {
|
|
|
195
209
|
return true;
|
|
196
210
|
}
|
|
197
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Get rate limit information for a provider
|
|
214
|
+
* @param {string} provider - Provider name
|
|
215
|
+
* @param {string} model - Model name (optional)
|
|
216
|
+
* @returns {Object} - {isRateLimited: boolean, resetTime: number, reason: string}
|
|
217
|
+
*/
|
|
218
|
+
getRateLimitInfo(provider, model) {
|
|
219
|
+
// If model not specified, check all models for this provider
|
|
220
|
+
if (!model) {
|
|
221
|
+
const providerPrefix = `${provider}:`;
|
|
222
|
+
let earliestReset = null;
|
|
223
|
+
let reason = null;
|
|
224
|
+
|
|
225
|
+
for (const key in this.rateLimits) {
|
|
226
|
+
if (key.startsWith(providerPrefix)) {
|
|
227
|
+
const limit = this.rateLimits[key];
|
|
228
|
+
if (limit && Date.now() < limit.resetTime) {
|
|
229
|
+
if (!earliestReset || limit.resetTime < earliestReset) {
|
|
230
|
+
earliestReset = limit.resetTime;
|
|
231
|
+
reason = limit.reason;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (earliestReset) {
|
|
238
|
+
return {
|
|
239
|
+
isRateLimited: true,
|
|
240
|
+
resetTime: earliestReset,
|
|
241
|
+
reason: reason
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
const key = `${provider}:${model}`;
|
|
246
|
+
const limit = this.rateLimits[key];
|
|
247
|
+
|
|
248
|
+
if (limit && Date.now() < limit.resetTime) {
|
|
249
|
+
return {
|
|
250
|
+
isRateLimited: true,
|
|
251
|
+
resetTime: limit.resetTime,
|
|
252
|
+
reason: limit.reason
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
isRateLimited: false,
|
|
259
|
+
resetTime: null,
|
|
260
|
+
reason: null
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
198
264
|
/**
|
|
199
265
|
* Get available provider (not rate limited)
|
|
200
266
|
* @param {Array} providers - Array of {provider, model, apiKey} objects
|
package/src/index.cjs
CHANGED
|
@@ -22,6 +22,7 @@ const requirementHelpers = require('./utils/requirement-helpers.js');
|
|
|
22
22
|
const requirementsParser = require('./utils/requirements-parser.js');
|
|
23
23
|
const updateChecker = require('./utils/update-checker.js');
|
|
24
24
|
const electronUpdateChecker = require('./utils/electron-update-checker.js');
|
|
25
|
+
const localization = require('./localization/index.js');
|
|
25
26
|
|
|
26
27
|
module.exports = {
|
|
27
28
|
CDPManager,
|
|
@@ -45,5 +46,6 @@ module.exports = {
|
|
|
45
46
|
...requirementHelpers,
|
|
46
47
|
...requirementsParser,
|
|
47
48
|
...updateChecker,
|
|
48
|
-
...electronUpdateChecker
|
|
49
|
+
...electronUpdateChecker,
|
|
50
|
+
...localization
|
|
49
51
|
};
|
package/src/index.js
CHANGED
|
@@ -13,7 +13,14 @@ export * from './utils/config-helpers.js';
|
|
|
13
13
|
export * from './utils/requirement-helpers.js';
|
|
14
14
|
export * from './utils/requirements-parser.js';
|
|
15
15
|
|
|
16
|
+
// Compliance
|
|
17
|
+
const CompliancePrompt = require('./compliance/compliance-prompt.js');
|
|
18
|
+
export { CompliancePrompt };
|
|
19
|
+
|
|
16
20
|
// UI Components
|
|
17
21
|
export * from './ui/ButtonComponents.js';
|
|
18
22
|
export * from './ui/StateManager.js';
|
|
19
23
|
export { ChatInterface } from './ui/ChatInterface.js';
|
|
24
|
+
|
|
25
|
+
// Localization
|
|
26
|
+
export * from './localization/index.js';
|