vibecodingmachine-core 2025.12.6-1702 → 2025.12.22-2230
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 +73 -127
- 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.js +4 -0
- package/src/llm/direct-llm-manager.cjs +110 -73
- 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
|
@@ -182,11 +182,43 @@ class AppleScriptManager {
|
|
|
182
182
|
set frontmost to true
|
|
183
183
|
delay 0.8
|
|
184
184
|
|
|
185
|
-
-- Try to find
|
|
185
|
+
-- Try to find model selection button with multiple possible texts
|
|
186
186
|
try
|
|
187
|
-
set
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
set buttonTexts to {"Select another model", "Switch model", "Change model", "Select model", "Try another model", "Choose model"}
|
|
188
|
+
set foundButton to missing value
|
|
189
|
+
|
|
190
|
+
-- Search for button in window 1
|
|
191
|
+
repeat with buttonText in buttonTexts
|
|
192
|
+
try
|
|
193
|
+
set matchingButtons to buttons of window 1 whose name contains buttonText
|
|
194
|
+
if (count of matchingButtons) > 0 then
|
|
195
|
+
set foundButton to item 1 of matchingButtons
|
|
196
|
+
exit repeat
|
|
197
|
+
end if
|
|
198
|
+
end try
|
|
199
|
+
end repeat
|
|
200
|
+
|
|
201
|
+
-- Also search in groups within window 1
|
|
202
|
+
if foundButton is missing value then
|
|
203
|
+
repeat with buttonText in buttonTexts
|
|
204
|
+
try
|
|
205
|
+
set allGroups to groups of window 1
|
|
206
|
+
repeat with grp in allGroups
|
|
207
|
+
try
|
|
208
|
+
set matchingButtons to buttons of grp whose name contains buttonText
|
|
209
|
+
if (count of matchingButtons) > 0 then
|
|
210
|
+
set foundButton to item 1 of matchingButtons
|
|
211
|
+
exit repeat
|
|
212
|
+
end if
|
|
213
|
+
end try
|
|
214
|
+
end repeat
|
|
215
|
+
if foundButton is not missing value then exit repeat
|
|
216
|
+
end try
|
|
217
|
+
end repeat
|
|
218
|
+
end if
|
|
219
|
+
|
|
220
|
+
if foundButton is not missing value then
|
|
221
|
+
click foundButton
|
|
190
222
|
delay 1.5
|
|
191
223
|
|
|
192
224
|
-- Look for model dropdown/menu items
|
|
@@ -195,20 +227,38 @@ class AppleScriptManager {
|
|
|
195
227
|
|
|
196
228
|
repeat with modelName in modelNames
|
|
197
229
|
try
|
|
198
|
-
-- Try to find and click this model option
|
|
199
|
-
set modelItems to
|
|
230
|
+
-- Try to find and click this model option in various locations
|
|
231
|
+
set modelItems to {}
|
|
232
|
+
|
|
233
|
+
-- Try menu items
|
|
234
|
+
try
|
|
235
|
+
set modelItems to menu items of menu 1 of window 1 whose name is modelName
|
|
236
|
+
end try
|
|
237
|
+
|
|
238
|
+
-- Try buttons if no menu items found
|
|
239
|
+
if (count of modelItems) = 0 then
|
|
240
|
+
try
|
|
241
|
+
set modelItems to buttons of window 1 whose name contains modelName
|
|
242
|
+
end try
|
|
243
|
+
end if
|
|
244
|
+
|
|
200
245
|
if (count of modelItems) > 0 then
|
|
201
246
|
click item 1 of modelItems
|
|
202
247
|
delay 0.8
|
|
203
248
|
|
|
204
249
|
-- Click "Accept all" or similar confirmation button
|
|
205
250
|
try
|
|
206
|
-
set
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
251
|
+
set acceptTexts to {"Accept", "OK", "Confirm", "Continue"}
|
|
252
|
+
repeat with acceptText in acceptTexts
|
|
253
|
+
try
|
|
254
|
+
set acceptButtons to buttons of window 1 whose name contains acceptText
|
|
255
|
+
if (count of acceptButtons) > 0 then
|
|
256
|
+
click item 1 of acceptButtons
|
|
257
|
+
delay 0.5
|
|
258
|
+
return "success:" & modelName
|
|
259
|
+
end if
|
|
260
|
+
end try
|
|
261
|
+
end repeat
|
|
212
262
|
end try
|
|
213
263
|
|
|
214
264
|
return "success:" & modelName
|
|
@@ -340,118 +390,14 @@ class AppleScriptManager {
|
|
|
340
390
|
end tell
|
|
341
391
|
`;
|
|
342
392
|
} else if (ide === 'cursor') {
|
|
343
|
-
// AppleScript for Cursor -
|
|
393
|
+
// AppleScript for Cursor - Using working shortcuts
|
|
344
394
|
appleScript = `
|
|
345
395
|
tell application "System Events"
|
|
346
396
|
tell process "Cursor"
|
|
347
397
|
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
|
|
398
|
+
delay 1.0
|
|
453
399
|
|
|
454
|
-
-- Open new chat session with Cmd+T to
|
|
400
|
+
-- Open new chat session with Cmd+T (proven to work)
|
|
455
401
|
key code 17 using {command down} -- Cmd+T
|
|
456
402
|
delay 1.0
|
|
457
403
|
|
|
@@ -462,8 +408,8 @@ except Exception as e:
|
|
|
462
408
|
delay 0.5
|
|
463
409
|
|
|
464
410
|
-- Press Cmd+Enter to send (more reliable than just Enter)
|
|
465
|
-
key code 36 using {command down}
|
|
466
|
-
delay 1
|
|
411
|
+
key code 36 using {command down} -- Cmd+Enter
|
|
412
|
+
delay 1.0
|
|
467
413
|
|
|
468
414
|
return "Message sent via AppleScript"
|
|
469
415
|
end tell
|
|
@@ -552,14 +498,14 @@ except Exception as e:
|
|
|
552
498
|
};
|
|
553
499
|
|
|
554
500
|
} catch (error) {
|
|
555
|
-
this.logger.log(
|
|
501
|
+
this.logger.log(`❌ AppleScript interaction failed for ${ideName}:`, error.message);
|
|
556
502
|
|
|
557
|
-
//
|
|
503
|
+
// Return failure - DO NOT fallback to simulation
|
|
558
504
|
return {
|
|
559
|
-
success:
|
|
560
|
-
|
|
561
|
-
message: `
|
|
562
|
-
note:
|
|
505
|
+
success: false,
|
|
506
|
+
error: error.message,
|
|
507
|
+
message: `Failed to send message to ${ideName}`,
|
|
508
|
+
note: `AppleScript automation failed. Error: ${error.message}`
|
|
563
509
|
};
|
|
564
510
|
}
|
|
565
511
|
}
|
|
@@ -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.js
CHANGED
|
@@ -13,6 +13,10 @@ 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';
|