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
|
@@ -54,21 +54,21 @@ end tell`;
|
|
|
54
54
|
try
|
|
55
55
|
-- Use Cmd+L to focus AI panel (faster and more reliable)
|
|
56
56
|
key code 37 using {command down} -- Cmd+L
|
|
57
|
-
delay
|
|
57
|
+
delay 2.0
|
|
58
58
|
|
|
59
59
|
-- Open new chat session with Cmd+T to prevent crashes
|
|
60
60
|
key code 17 using {command down} -- Cmd+T
|
|
61
|
-
delay
|
|
61
|
+
delay 3.0
|
|
62
62
|
|
|
63
63
|
-- Skip clearing text to avoid selecting file content
|
|
64
64
|
-- The chat input should be empty when opening new chat session
|
|
65
65
|
|
|
66
66
|
-- Wait additional time for chat input field to be fully ready
|
|
67
|
-
delay 1.
|
|
67
|
+
delay 1.5
|
|
68
68
|
|
|
69
69
|
-- Type the message
|
|
70
70
|
keystroke "${escapedText}"
|
|
71
|
-
delay
|
|
71
|
+
delay 2.0
|
|
72
72
|
|
|
73
73
|
-- Send with Cmd+Enter (standard for chat interfaces)
|
|
74
74
|
key code 36 using {command down}
|
|
@@ -86,33 +86,41 @@ end tell`;
|
|
|
86
86
|
on error
|
|
87
87
|
-- STRATEGY 2: Command Palette Approach (Fallback 1) - NO AI PANEL TOGGLE
|
|
88
88
|
try
|
|
89
|
-
-- Step 1:
|
|
89
|
+
-- Step 1: Press Escape first to ensure we're starting clean
|
|
90
|
+
key code 53 -- Escape
|
|
91
|
+
delay 0.5
|
|
92
|
+
|
|
93
|
+
-- Step 2: Open Command Palette (Cmd+Shift+P)
|
|
90
94
|
key code 35 using {command down, shift down} -- Cmd+Shift+P
|
|
91
|
-
delay 0
|
|
95
|
+
delay 1.0
|
|
92
96
|
|
|
93
|
-
-- Step
|
|
97
|
+
-- Step 3: Type "View: Focus into Secondary Side Bar" to focus AI panel
|
|
94
98
|
keystroke "View: Focus into Secondary Side Bar"
|
|
95
|
-
delay 0
|
|
99
|
+
delay 1.0
|
|
96
100
|
|
|
97
|
-
-- Step
|
|
101
|
+
-- Step 4: Press Enter to execute the command
|
|
98
102
|
key code 36 -- Enter
|
|
99
|
-
delay 2.
|
|
103
|
+
delay 2.5
|
|
100
104
|
|
|
101
|
-
-- Step
|
|
105
|
+
-- Step 5: CRITICAL - Press Escape to close command palette
|
|
106
|
+
key code 53 -- Escape
|
|
107
|
+
delay 0.5
|
|
108
|
+
|
|
109
|
+
-- Step 6: Open new chat session with Cmd+T to prevent crashes
|
|
102
110
|
key code 17 using {command down} -- Cmd+T
|
|
103
|
-
delay
|
|
111
|
+
delay 3.0
|
|
104
112
|
|
|
105
|
-
-- Step
|
|
113
|
+
-- Step 7: Skip clearing text to avoid selecting file content
|
|
106
114
|
-- The chat input should be empty when opening new chat session
|
|
107
115
|
|
|
108
|
-
-- Step
|
|
109
|
-
delay 1.
|
|
116
|
+
-- Step 8: Wait additional time for chat input field to be fully ready
|
|
117
|
+
delay 1.5
|
|
110
118
|
|
|
111
|
-
-- Step
|
|
119
|
+
-- Step 9: Type the message
|
|
112
120
|
keystroke "${escapedText}"
|
|
113
|
-
delay
|
|
121
|
+
delay 2.0
|
|
114
122
|
|
|
115
|
-
-- Step
|
|
123
|
+
-- Step 10: Send with Cmd+Enter (standard for chat interfaces)
|
|
116
124
|
key code 36 using {command down}
|
|
117
125
|
delay 0.5
|
|
118
126
|
|
|
@@ -23,7 +23,7 @@ class ProviderManager {
|
|
|
23
23
|
if (fs.existsSync(this.rateLimitFile)) {
|
|
24
24
|
const data = fs.readFileSync(this.rateLimitFile, 'utf8');
|
|
25
25
|
const limits = JSON.parse(data);
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
// Auto-cleanup expired rate limits
|
|
28
28
|
const now = Date.now();
|
|
29
29
|
let cleaned = false;
|
|
@@ -33,12 +33,12 @@ class ProviderManager {
|
|
|
33
33
|
cleaned = true;
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
// Save cleaned data if any expired limits were removed
|
|
38
38
|
if (cleaned) {
|
|
39
39
|
this.saveRateLimitsInternal(limits);
|
|
40
40
|
}
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
return limits;
|
|
43
43
|
}
|
|
44
44
|
} catch (err) {
|
|
@@ -46,7 +46,7 @@ class ProviderManager {
|
|
|
46
46
|
}
|
|
47
47
|
return {};
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
/**
|
|
51
51
|
* Internal save without reload (to prevent infinite loop during cleanup)
|
|
52
52
|
*/
|
|
@@ -86,33 +86,107 @@ class ProviderManager {
|
|
|
86
86
|
* "Session limit reached ∙ resets 12pm" -> ms until 12pm today/tomorrow
|
|
87
87
|
*/
|
|
88
88
|
parseRateLimitDuration(errorMessage) {
|
|
89
|
+
// Gemini / Google AI style: "You can resume using this model at 1/12/2026, 4:07:27 PM"
|
|
90
|
+
// Also seen as: "resume using this model at <date>, <time>"
|
|
91
|
+
const resumeAtMatch = errorMessage.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);
|
|
92
|
+
if (resumeAtMatch) {
|
|
93
|
+
try {
|
|
94
|
+
const datePart = resumeAtMatch[1];
|
|
95
|
+
const timePart = resumeAtMatch[2];
|
|
96
|
+
const meridiem = resumeAtMatch[3];
|
|
97
|
+
const parsed = new Date(`${datePart}, ${timePart} ${meridiem.toUpperCase()}`);
|
|
98
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
99
|
+
const ms = parsed.getTime() - Date.now();
|
|
100
|
+
return ms > 0 ? ms : 0;
|
|
101
|
+
}
|
|
102
|
+
} catch (_) { }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check for "resets [Month] [Day] at [Time]" format (e.g. "Spending cap reached resets Jan 17 at 12pm")
|
|
106
|
+
const dateMatch = errorMessage.match(/resets\s+([a-zA-Z]+)\s+(\d{1,2})\s+at\s+(\d{1,2}(?::\d{2})?)\s*(am|pm)?/i);
|
|
107
|
+
if (dateMatch) {
|
|
108
|
+
try {
|
|
109
|
+
const monthStr = dateMatch[1]; // Jan, February, etc.
|
|
110
|
+
const day = parseInt(dateMatch[2]);
|
|
111
|
+
const timeStr = dateMatch[3];
|
|
112
|
+
const meridiem = dateMatch[4] ? dateMatch[4].toLowerCase() : null;
|
|
113
|
+
|
|
114
|
+
// Parse time
|
|
115
|
+
let [hours, minutes] = timeStr.split(':').map(n => parseInt(n));
|
|
116
|
+
if (!minutes || Number.isNaN(minutes)) minutes = 0;
|
|
117
|
+
|
|
118
|
+
if (meridiem === 'pm' && hours !== 12) hours += 12;
|
|
119
|
+
if (meridiem === 'am' && hours === 12) hours = 0;
|
|
120
|
+
|
|
121
|
+
// Parse month
|
|
122
|
+
const months = {
|
|
123
|
+
jan: 0, january: 0,
|
|
124
|
+
feb: 1, february: 1,
|
|
125
|
+
mar: 2, march: 2,
|
|
126
|
+
apr: 3, april: 3,
|
|
127
|
+
may: 4,
|
|
128
|
+
jun: 5, june: 5,
|
|
129
|
+
jul: 6, july: 6,
|
|
130
|
+
aug: 7, august: 7,
|
|
131
|
+
sep: 8, sept: 8, september: 8,
|
|
132
|
+
oct: 9, october: 9,
|
|
133
|
+
nov: 10, november: 10,
|
|
134
|
+
dec: 11, december: 11
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const monthIndex = months[monthStr.toLowerCase().substring(0, 3)] ?? months[monthStr.toLowerCase()];
|
|
138
|
+
|
|
139
|
+
if (monthIndex !== undefined) {
|
|
140
|
+
const currentYear = new Date().getFullYear();
|
|
141
|
+
let parsed = new Date(currentYear, monthIndex, day, hours, minutes);
|
|
142
|
+
const now = Date.now();
|
|
143
|
+
|
|
144
|
+
// Logic to handle year crossing (reset in Jan, currently Dec)
|
|
145
|
+
if (parsed.getTime() < now) {
|
|
146
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
147
|
+
if (parsed.getTime() < now) {
|
|
148
|
+
const nextYearDate = new Date(parsed);
|
|
149
|
+
nextYearDate.setFullYear(nextYearDate.getFullYear() + 1);
|
|
150
|
+
if (nextYearDate.getTime() > now) {
|
|
151
|
+
parsed = nextYearDate;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const ms = parsed.getTime() - now;
|
|
158
|
+
return ms > 0 ? ms : 0;
|
|
159
|
+
}
|
|
160
|
+
} catch (_) { }
|
|
161
|
+
}
|
|
162
|
+
|
|
89
163
|
// Check for Claude Code session limit format: "Session limit reached ∙ resets 12pm"
|
|
90
164
|
const sessionMatch = errorMessage.match(/resets?\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)?/i);
|
|
91
165
|
if (sessionMatch) {
|
|
92
166
|
let hour = parseInt(sessionMatch[1]);
|
|
93
167
|
const minute = sessionMatch[2] ? parseInt(sessionMatch[2]) : 0;
|
|
94
168
|
const meridiem = sessionMatch[3] ? sessionMatch[3].toLowerCase() : null;
|
|
95
|
-
|
|
169
|
+
|
|
96
170
|
// Convert to 24-hour format
|
|
97
171
|
if (meridiem === 'pm' && hour !== 12) {
|
|
98
172
|
hour += 12;
|
|
99
173
|
} else if (meridiem === 'am' && hour === 12) {
|
|
100
174
|
hour = 0;
|
|
101
175
|
}
|
|
102
|
-
|
|
176
|
+
|
|
103
177
|
// Calculate reset time
|
|
104
178
|
const now = new Date();
|
|
105
179
|
const resetTime = new Date(now);
|
|
106
180
|
resetTime.setHours(hour, minute, 0, 0);
|
|
107
|
-
|
|
181
|
+
|
|
108
182
|
// If reset time is in the past, it's tomorrow
|
|
109
183
|
if (resetTime <= now) {
|
|
110
184
|
resetTime.setDate(resetTime.getDate() + 1);
|
|
111
185
|
}
|
|
112
|
-
|
|
186
|
+
|
|
113
187
|
return resetTime.getTime() - now.getTime();
|
|
114
188
|
}
|
|
115
|
-
|
|
189
|
+
|
|
116
190
|
// Match patterns like "15m5.472s" or "1h30m" or "45s"
|
|
117
191
|
const timeMatch = errorMessage.match(/try again in ([\d.]+h)?\s?([\d.]+m)?\s?([\d.]+s)?/i);
|
|
118
192
|
if (!timeMatch) return null;
|
|
@@ -147,8 +221,11 @@ class ProviderManager {
|
|
|
147
221
|
* @param {string} errorMessage - Full error message containing duration
|
|
148
222
|
*/
|
|
149
223
|
markRateLimited(provider, model, errorMessage) {
|
|
224
|
+
// Normalize model: if model is missing/undefined, assume it's the provider name.
|
|
225
|
+
model = model || provider;
|
|
226
|
+
|
|
150
227
|
let duration = this.parseRateLimitDuration(errorMessage);
|
|
151
|
-
if (
|
|
228
|
+
if (duration === null || duration === undefined) {
|
|
152
229
|
console.warn(`Could not parse rate limit duration from: ${errorMessage}`);
|
|
153
230
|
// Default to 15 minutes if we can't parse
|
|
154
231
|
duration = 15 * 60 * 1000;
|
|
@@ -162,6 +239,7 @@ class ProviderManager {
|
|
|
162
239
|
model,
|
|
163
240
|
resetTime,
|
|
164
241
|
resetDate: new Date(resetTime).toISOString(),
|
|
242
|
+
reason: errorMessage,
|
|
165
243
|
markedAt: new Date().toISOString()
|
|
166
244
|
};
|
|
167
245
|
|
|
@@ -196,17 +274,28 @@ class ProviderManager {
|
|
|
196
274
|
const key = `${provider}:${model}`;
|
|
197
275
|
const limit = this.rateLimits[key];
|
|
198
276
|
|
|
199
|
-
if (
|
|
277
|
+
if (limit) {
|
|
278
|
+
// Check if rate limit has expired
|
|
279
|
+
if (Date.now() >= limit.resetTime) {
|
|
280
|
+
// Clean up expired rate limit
|
|
281
|
+
delete this.rateLimits[key];
|
|
282
|
+
this.saveRateLimits();
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
200
285
|
|
|
201
|
-
|
|
202
|
-
if (Date.now() >= limit.resetTime) {
|
|
203
|
-
// Clean up expired rate limit
|
|
204
|
-
delete this.rateLimits[key];
|
|
205
|
-
this.saveRateLimits();
|
|
206
|
-
return false;
|
|
286
|
+
return true;
|
|
207
287
|
}
|
|
208
288
|
|
|
209
|
-
|
|
289
|
+
// Fall back to any provider-level entries (handles older or model-agnostic keys)
|
|
290
|
+
const providerPrefix = `${provider}:`;
|
|
291
|
+
for (const k in this.rateLimits) {
|
|
292
|
+
if (k.startsWith(providerPrefix)) {
|
|
293
|
+
const l = this.rateLimits[k];
|
|
294
|
+
if (l && Date.now() < l.resetTime) return true;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return false;
|
|
210
299
|
}
|
|
211
300
|
|
|
212
301
|
/**
|
|
@@ -252,6 +341,31 @@ class ProviderManager {
|
|
|
252
341
|
reason: limit.reason
|
|
253
342
|
};
|
|
254
343
|
}
|
|
344
|
+
|
|
345
|
+
// Fallback: check other keys for this provider and return earliest reset
|
|
346
|
+
const providerPrefix = `${provider}:`;
|
|
347
|
+
let earliestReset = null;
|
|
348
|
+
let reason = null;
|
|
349
|
+
|
|
350
|
+
for (const k in this.rateLimits) {
|
|
351
|
+
if (k.startsWith(providerPrefix)) {
|
|
352
|
+
const l = this.rateLimits[k];
|
|
353
|
+
if (l && Date.now() < l.resetTime) {
|
|
354
|
+
if (!earliestReset || l.resetTime < earliestReset) {
|
|
355
|
+
earliestReset = l.resetTime;
|
|
356
|
+
reason = l.reason;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (earliestReset) {
|
|
363
|
+
return {
|
|
364
|
+
isRateLimited: true,
|
|
365
|
+
resetTime: earliestReset,
|
|
366
|
+
reason
|
|
367
|
+
};
|
|
368
|
+
}
|
|
255
369
|
}
|
|
256
370
|
|
|
257
371
|
return {
|
|
@@ -285,9 +399,25 @@ class ProviderManager {
|
|
|
285
399
|
const key = `${provider}:${model}`;
|
|
286
400
|
const limit = this.rateLimits[key];
|
|
287
401
|
|
|
288
|
-
if (
|
|
402
|
+
if (limit && Date.now() < limit.resetTime) {
|
|
403
|
+
const remaining = limit.resetTime - Date.now();
|
|
404
|
+
return remaining > 0 ? remaining : null;
|
|
405
|
+
}
|
|
289
406
|
|
|
290
|
-
|
|
407
|
+
// Fallback: check other keys for this provider (handles legacy or model-agnostic entries)
|
|
408
|
+
const providerPrefix = `${provider}:`;
|
|
409
|
+
let earliest = null;
|
|
410
|
+
for (const k in this.rateLimits) {
|
|
411
|
+
if (k.startsWith(providerPrefix)) {
|
|
412
|
+
const l = this.rateLimits[k];
|
|
413
|
+
if (l && Date.now() < l.resetTime) {
|
|
414
|
+
if (!earliest || l.resetTime < earliest) earliest = l.resetTime;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (!earliest) return null;
|
|
420
|
+
const remaining = earliest - Date.now();
|
|
291
421
|
return remaining > 0 ? remaining : null;
|
|
292
422
|
}
|
|
293
423
|
|
|
@@ -356,7 +486,7 @@ class ProviderManager {
|
|
|
356
486
|
*/
|
|
357
487
|
recordPerformance(provider, model, durationMs) {
|
|
358
488
|
const key = `${provider}:${model}`;
|
|
359
|
-
|
|
489
|
+
|
|
360
490
|
if (!this.performance[key]) {
|
|
361
491
|
this.performance[key] = {
|
|
362
492
|
provider,
|
|
@@ -365,19 +495,19 @@ class ProviderManager {
|
|
|
365
495
|
avgSpeed: 0
|
|
366
496
|
};
|
|
367
497
|
}
|
|
368
|
-
|
|
498
|
+
|
|
369
499
|
const perf = this.performance[key];
|
|
370
500
|
perf.samples.push(durationMs);
|
|
371
|
-
|
|
501
|
+
|
|
372
502
|
// Keep only last 10 samples
|
|
373
503
|
if (perf.samples.length > 10) {
|
|
374
504
|
perf.samples.shift();
|
|
375
505
|
}
|
|
376
|
-
|
|
506
|
+
|
|
377
507
|
// Calculate average
|
|
378
508
|
perf.avgSpeed = perf.samples.reduce((a, b) => a + b, 0) / perf.samples.length;
|
|
379
509
|
perf.lastUsed = Date.now();
|
|
380
|
-
|
|
510
|
+
|
|
381
511
|
this.savePerformance();
|
|
382
512
|
}
|
|
383
513
|
|
|
@@ -388,10 +518,10 @@ class ProviderManager {
|
|
|
388
518
|
*/
|
|
389
519
|
getFastestAvailable(providers) {
|
|
390
520
|
const available = providers.filter(p => !this.isRateLimited(p.provider, p.model));
|
|
391
|
-
|
|
521
|
+
|
|
392
522
|
if (available.length === 0) return null;
|
|
393
523
|
if (available.length === 1) return available[0];
|
|
394
|
-
|
|
524
|
+
|
|
395
525
|
// Sort by average speed (fastest first)
|
|
396
526
|
available.sort((a, b) => {
|
|
397
527
|
const aKey = `${a.provider}:${a.model}`;
|
|
@@ -402,7 +532,7 @@ class ProviderManager {
|
|
|
402
532
|
const bSpeed = (bAvg ?? b.estimatedSpeed ?? Infinity);
|
|
403
533
|
return aSpeed - bSpeed;
|
|
404
534
|
});
|
|
405
|
-
|
|
535
|
+
|
|
406
536
|
return available[0];
|
|
407
537
|
}
|
|
408
538
|
|